1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2008-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 // file selection dialogs
17 
18 #include "C4Include.h"
19 #include "gui/C4FileSelDlg.h"
20 
21 #include "C4Version.h"
22 #include "graphics/C4Draw.h"
23 #include "graphics/C4GraphicsResource.h" // only for single use of ::GraphicsResource.fctOKCancel below...
24 
25 #ifdef _WIN32
26 #ifndef _WIN32_IE
27 #define _WIN32_IE 0x0400
28 #endif
29 #undef MK_ALT
30 #include <shlobj.h>
31 #ifndef CSIDL_MYPICTURES
32 #define CSIDL_MYPICTURES 0x0027
33 #endif
34 #endif
35 
36 // def 1 if gfx loading works in background thread. Right now, it doesn't
37 // C4Group and C4Surface just don't like it
38 // So for now, the loading will be done in 1/10 of the frames in OnIdle of the dialog
39 #define USE_BACKGROUND_THREAD_LOAD 0
40 
41 
42 // ---------------------------------------------------
43 // C4FileSelDlg::ListItem
44 
ListItem(const char * szFilename)45 C4FileSelDlg::ListItem::ListItem(const char *szFilename) : C4GUI::Control(C4Rect(0,0,0,0))
46 {
47 	if (szFilename) sFilename.Copy(szFilename); else sFilename.Clear();
48 }
49 
50 C4FileSelDlg::ListItem::~ListItem() = default;
51 
52 // ---------------------------------------------------
53 // C4FileSelDlg::DefaultListItem
54 
DefaultListItem(const char * szFilename,bool fTruncateExtension,bool fCheckbox,bool fGrayed,C4GUI::Icons eIcon)55 C4FileSelDlg::DefaultListItem::DefaultListItem(const char *szFilename, bool fTruncateExtension, bool fCheckbox, bool fGrayed, C4GUI::Icons eIcon)
56 		: C4FileSelDlg::ListItem(szFilename), pLbl(nullptr), pCheck(nullptr), pKeyCheck(nullptr), fGrayed(fGrayed)
57 {
58 	StdStrBuf sLabel; if (szFilename) sLabel.Ref(::GetFilename(szFilename)); else sLabel.Ref(LoadResStr("IDS_CTL_NONE"));
59 	if (szFilename && fTruncateExtension)
60 	{
61 		sLabel.Copy();
62 		char *szFilename = sLabel.GrabPointer();
63 		RemoveExtension(szFilename);
64 		sLabel.Take(szFilename);
65 	}
66 	rcBounds.Hgt = ::GraphicsResource.TextFont.GetLineHeight();
67 	UpdateSize();
68 	C4GUI::ComponentAligner caMain(GetContainedClientRect(),0,0);
69 	int32_t iHeight = caMain.GetInnerHeight();
70 	if (fCheckbox)
71 	{
72 		pCheck = new C4GUI::CheckBox(caMain.GetFromLeft(iHeight), nullptr, false);
73 		if (fGrayed) pCheck->SetEnabled(false);
74 		AddElement(pCheck);
75 		pKeyCheck = new C4KeyBinding(C4KeyCodeEx(K_SPACE), "FileSelToggleFileActive", KEYSCOPE_Gui,
76 		                             new C4GUI::ControlKeyCB<ListItem>(*this, &ListItem::UserToggleCheck), C4CustomKey::PRIO_Ctrl);
77 	}
78 	C4GUI::Icon *pIco = new C4GUI::Icon(caMain.GetFromLeft(iHeight), eIcon);
79 	AddElement(pIco);
80 	pLbl = new C4GUI::Label(sLabel.getData(), caMain.GetAll(), ALeft, fGrayed ? C4GUI_CheckboxDisabledFontClr : C4GUI_CheckboxFontClr);
81 	AddElement(pLbl);
82 }
83 
~DefaultListItem()84 C4FileSelDlg::DefaultListItem::~DefaultListItem()
85 {
86 	if (pKeyCheck) delete pKeyCheck;
87 }
88 
UpdateOwnPos()89 void C4FileSelDlg::DefaultListItem::UpdateOwnPos()
90 {
91 	BaseClass::UpdateOwnPos();
92 	if (!pLbl) return;
93 	C4GUI::ComponentAligner caMain(GetContainedClientRect(),0,0);
94 	caMain.GetFromLeft(caMain.GetInnerHeight()*(1+!!pCheck));
95 	pLbl->SetBounds(caMain.GetAll());
96 }
97 
IsChecked() const98 bool C4FileSelDlg::DefaultListItem::IsChecked() const
99 {
100 	return pCheck ? pCheck->GetChecked() : false;
101 }
102 
SetChecked(bool fChecked)103 void C4FileSelDlg::DefaultListItem::SetChecked(bool fChecked)
104 {
105 	// store new state in checkbox
106 	if (pCheck) pCheck->SetChecked(fChecked);
107 }
108 
UserToggleCheck()109 bool C4FileSelDlg::DefaultListItem::UserToggleCheck()
110 {
111 	// toggle if possible
112 	if (pCheck && !IsGrayed())
113 	{
114 		pCheck->ToggleCheck(true);
115 		return true;
116 	}
117 	return false;
118 }
119 
120 
121 
122 
123 // ---------------------------------------------------
124 // C4FileSelDlg
125 
C4FileSelDlg(const char * szRootPath,const char * szTitle,C4FileSel_BaseCB * pSelCallback,bool fInitElements)126 C4FileSelDlg::C4FileSelDlg(const char *szRootPath, const char *szTitle, C4FileSel_BaseCB *pSelCallback, bool fInitElements)
127 		: C4GUI::Dialog(Clamp(C4GUI::GetScreenWdt()*2/3+10, 300,600), Clamp(C4GUI::GetScreenHgt()*2/3+10, 220,500), szTitle, false),
128 		pLocationComboBox(nullptr), pFileListBox(nullptr), pSelectionInfoBox(nullptr), btnOK(nullptr), pLocations(nullptr), iLocationCount(0), pSelection(nullptr), pSelCallback(pSelCallback)
129 {
130 	sTitle.Copy(szTitle);
131 	// key bindings
132 	pKeyRefresh = new C4KeyBinding(C4KeyCodeEx(K_F5), "FileSelReload", KEYSCOPE_Gui,
133 	                               new C4GUI::DlgKeyCB<C4FileSelDlg>(*this, &C4FileSelDlg::KeyRefresh), C4CustomKey::PRIO_CtrlOverride);
134 	pKeyEnterOverride = new C4KeyBinding(C4KeyCodeEx(K_RETURN), "FileSelConfirm", KEYSCOPE_Gui,
135 	                                     new C4GUI::DlgKeyCB<C4FileSelDlg>(*this, &C4FileSelDlg::KeyEnter), C4CustomKey::PRIO_CtrlOverride);
136 	if (fInitElements) InitElements();
137 	sPath.Copy(szRootPath);
138 }
139 
InitElements()140 void C4FileSelDlg::InitElements()
141 {
142 	UpdateSize();
143 	CStdFont *pUseFont = &(::GraphicsResource.TextFont);
144 	// main calcs
145 	bool fHasOptions = HasExtraOptions();
146 	C4GUI::ComponentAligner caMain(GetClientRect(), 0,0, true);
147 	C4Rect rcOptions;
148 	C4GUI::ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt, 2*C4GUI_DefButton2Wdt+4*C4GUI_DefButton2HSpace),C4GUI_DefButton2HSpace,(C4GUI_ButtonAreaHgt-C4GUI_ButtonHgt)/2);
149 	if (fHasOptions) rcOptions = caMain.GetFromBottom(pUseFont->GetLineHeight() + 2*C4GUI_DefDlgSmallIndent);
150 	C4GUI::ComponentAligner caUpperArea(caMain.GetAll(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true);
151 	// create file selection area
152 	if (iLocationCount)
153 	{
154 		C4GUI::ComponentAligner caLocations(caUpperArea.GetFromTop(C4GUI::ComboBox::GetDefaultHeight() + 2*C4GUI_DefDlgSmallIndent), C4GUI_DefDlgIndent,C4GUI_DefDlgSmallIndent, false);
155 		StdStrBuf sText(LoadResStr("IDS_TEXT_LOCATION"));
156 		AddElement(new C4GUI::Label(sText.getData(), caLocations.GetFromLeft(pUseFont->GetTextWidth(sText.getData())), ALeft));
157 		pLocationComboBox = new C4GUI::ComboBox(caLocations.GetAll());
158 		pLocationComboBox->SetComboCB(new C4GUI::ComboBox_FillCallback<C4FileSelDlg>(this, &C4FileSelDlg::OnLocationComboFill, &C4FileSelDlg::OnLocationComboSelChange));
159 		pLocationComboBox->SetText(pLocations[0].sName.getData());
160 	}
161 	// create file selection area
162 	bool fHasPreview = HasPreviewArea();
163 	pFileListBox = new C4GUI::ListBox(fHasPreview ? caUpperArea.GetFromLeft(caUpperArea.GetWidth()/2) : caUpperArea.GetAll(), GetFileSelColWidth());
164 	pFileListBox ->SetSelectionChangeCallbackFn(new C4GUI::CallbackHandler<C4FileSelDlg>(this, &C4FileSelDlg::OnSelChange));
165 	pFileListBox ->SetSelectionDblClickFn(new C4GUI::CallbackHandler<C4FileSelDlg>(this, &C4FileSelDlg::OnSelDblClick));
166 	if (fHasPreview)
167 	{
168 		caUpperArea.ExpandLeft(C4GUI_DefDlgIndent);
169 		pSelectionInfoBox = new C4GUI::TextWindow(caUpperArea.GetAll());
170 		pSelectionInfoBox->SetDecoration(true, true, nullptr, true);
171 	}
172 	// create button area
173 	C4GUI::Button *btnAbort = new C4GUI::CancelButton(caButtonArea.GetFromRight(C4GUI_DefButton2Wdt));
174 	btnOK = new C4GUI::OKButton(caButtonArea.GetFromRight(C4GUI_DefButton2Wdt));
175 	// add components in tab order
176 	if (pLocationComboBox) AddElement(pLocationComboBox);
177 	AddElement(pFileListBox);
178 	if (pSelectionInfoBox) AddElement(pSelectionInfoBox);
179 	if (fHasOptions) AddExtraOptions(rcOptions);
180 	AddElement(btnOK);
181 	AddElement(btnAbort);
182 	SetFocus(pFileListBox, false);
183 	// no selection yet
184 	UpdateSelection();
185 }
186 
~C4FileSelDlg()187 C4FileSelDlg::~C4FileSelDlg()
188 {
189 	delete [] pLocations;
190 	if (pSelCallback) delete pSelCallback;
191 	delete pKeyEnterOverride;
192 	delete pKeyRefresh;
193 }
194 
OnLocationComboFill(C4GUI::ComboBox_FillCB * pFiller)195 void C4FileSelDlg::OnLocationComboFill(C4GUI::ComboBox_FillCB *pFiller)
196 {
197 	// Add all locations
198 	for (int32_t i=0; i<iLocationCount; ++i)
199 		pFiller->AddEntry(pLocations[i].sName.getData(), i);
200 }
201 
OnLocationComboSelChange(C4GUI::ComboBox * pForCombo,int32_t idNewSelection)202 bool C4FileSelDlg::OnLocationComboSelChange(C4GUI::ComboBox *pForCombo, int32_t idNewSelection)
203 {
204 	SetCurrentLocation(idNewSelection, true);
205 	// No text change by caller; text alread changed by SetCurrentLocation
206 	return true;
207 }
208 
SetPath(const char * szNewPath,bool fRefresh)209 void C4FileSelDlg::SetPath(const char *szNewPath, bool fRefresh)
210 {
211 	sPath.Copy(szNewPath);
212 	if (fRefresh && IsShown()) UpdateFileList();
213 }
214 
OnShown()215 void C4FileSelDlg::OnShown()
216 {
217 	BaseClass::OnShown();
218 	// load files
219 	UpdateFileList();
220 }
221 
UserClose(bool fOK)222 void C4FileSelDlg::UserClose(bool fOK)
223 {
224 	if (!fOK || pSelection)
225 	{
226 		// allow OK only if something is sth is selected
227 		Close(fOK);
228 	}
229 	else
230 	{
231 		GetScreen()->ShowErrorMessage(LoadResStr("IDS_ERR_PLEASESELECTAFILEFIRST"));
232 	}
233 }
234 
OnClosed(bool fOK)235 void C4FileSelDlg::OnClosed(bool fOK)
236 {
237 	if (fOK && pSelection && pSelCallback)
238 		pSelCallback->OnFileSelected(pSelection->GetFilename());
239 	// base call: Might delete dlg
240 	BaseClass::OnClosed(fOK);
241 }
242 
OnSelDblClick(class C4GUI::Element * pEl)243 void C4FileSelDlg::OnSelDblClick(class C4GUI::Element *pEl)
244 {
245 	// item double-click: confirms with this file in single mode; toggles selection in multi mode
246 	if (IsMultiSelection())
247 	{
248 		ListItem *pItem = static_cast<ListItem *>(pEl);
249 		pItem->UserToggleCheck();
250 	}
251 	else
252 		UserClose(true);
253 }
254 
CreateListItem(const char * szFilename)255 C4FileSelDlg::ListItem *C4FileSelDlg::CreateListItem(const char *szFilename)
256 {
257 	// Default list item
258 	if (szFilename)
259 		return new DefaultListItem(szFilename, !!GetFileMask(), IsMultiSelection(), IsItemGrayed(szFilename), GetFileItemIcon());
260 	else
261 		return new DefaultListItem(nullptr, false, IsMultiSelection(), false, GetFileItemIcon());
262 }
263 
UpdateFileList()264 void C4FileSelDlg::UpdateFileList()
265 {
266 	BeginFileListUpdate();
267 	// reload files
268 	C4GUI::Element *pEl;
269 	while ((pEl = pFileListBox->GetFirst())) delete pEl;
270 	// file items
271 	StdStrBuf sSearch;
272 	const char *szFileMask = GetFileMask();
273 	for (DirectoryIterator iter(sPath.getData()); *iter; ++iter)
274 		if (!szFileMask || WildcardListMatch(szFileMask, *iter))
275 			pFileListBox->AddElement(CreateListItem(*iter));
276 	// none-item?
277 	if (HasNoneItem())
278 	{
279 		pFileListBox->AddElement(CreateListItem(nullptr));
280 	}
281 	// list now done
282 	EndFileListUpdate();
283 	// path into title
284 	const char *szPath = sPath.getData();
285 	SetTitle(*szPath ? FormatString("%s [%s]", sTitle.getData(), szPath).getData() : sTitle.getData());
286 	// initial no-selection
287 	UpdateSelection();
288 }
289 
UpdateSelection()290 void C4FileSelDlg::UpdateSelection()
291 {
292 	// update selection from list
293 	pSelection = static_cast<ListItem *>(pFileListBox->GetSelectedItem());
294 	// OK button only available if selection
295 	// SetEnabled would look a lot better here, but it doesn't exist yet :(
296 	// selection preview, if enabled
297 	if (pSelectionInfoBox)
298 	{
299 		// default empty
300 		pSelectionInfoBox->ClearText(false);
301 		if (!pSelection) { pSelectionInfoBox->UpdateHeight(); return; }
302 		// add selection description
303 		if (pSelection->GetFilename())
304 			pSelectionInfoBox->AddTextLine(pSelection->GetFilename(), &::GraphicsResource.TextFont, C4GUI_MessageFontClr, true, false);
305 	}
306 }
307 
SetSelection(const char * szNewSelection,bool fFilenameOnly)308 void C4FileSelDlg::SetSelection(const char *szNewSelection, bool fFilenameOnly)
309 {
310 	// check all selected definitions
311 	for (ListItem *pFileItem = static_cast<ListItem *>(pFileListBox->GetFirst()); pFileItem; pFileItem = static_cast<ListItem *>(pFileItem->GetNext()))
312 	{
313 		const char *szFileItemFilename = pFileItem->GetFilename();
314 		if (fFilenameOnly) szFileItemFilename = GetFilename(szFileItemFilename);
315 		pFileItem->SetChecked(SIsModule(szNewSelection, szFileItemFilename));
316 	}
317 }
318 
GetSelection(const char * szFixedSelection,bool fFilenameOnly) const319 StdStrBuf C4FileSelDlg::GetSelection(const char *szFixedSelection, bool fFilenameOnly) const
320 {
321 	StdStrBuf sResult;
322 	if (!IsMultiSelection())
323 	{
324 		// get single selected file for single selection dlg
325 		if (pSelection) sResult.Copy(fFilenameOnly ? GetFilename(pSelection->GetFilename()) : pSelection->GetFilename());
326 	}
327 	else
328 	{
329 		// force fixed selection first
330 		if (szFixedSelection) sResult.Append(szFixedSelection);
331 		//  get ';'-separated list for multi selection dlg
332 		for (ListItem *pFileItem = static_cast<ListItem *>(pFileListBox->GetFirst()); pFileItem; pFileItem = static_cast<ListItem *>(pFileItem->GetNext()))
333 			if (pFileItem->IsChecked())
334 			{
335 				const char *szAppendFilename = pFileItem->GetFilename();
336 				if (fFilenameOnly) szAppendFilename = GetFilename(szAppendFilename);
337 				// prevent adding entries twice (especially those from the fixed selection list)
338 				if (!SIsModule(sResult.getData(), szAppendFilename))
339 				{
340 					if (sResult.getLength()) sResult.AppendChar(';');
341 					sResult.Append(szAppendFilename);
342 				}
343 			}
344 	}
345 	return sResult;
346 }
347 
AddLocation(const char * szName,const char * szPath)348 void C4FileSelDlg::AddLocation(const char *szName, const char *szPath)
349 {
350 	// add to list
351 	int32_t iNewLocCount = iLocationCount+1;
352 	Location *pNewLocations = new Location[iNewLocCount];
353 	for (int32_t i=0; i<iLocationCount; ++i) pNewLocations[i] = pLocations[i];
354 	pNewLocations[iLocationCount].sName.Copy(szName);
355 	pNewLocations[iLocationCount].sPath.Copy(szPath);
356 	delete [] pLocations; pLocations = pNewLocations; iLocationCount = iNewLocCount;
357 	// first location? Then set path to this
358 	if (iLocationCount == 1) SetPath(szPath, false);
359 }
360 
AddCheckedLocation(const char * szName,const char * szPath)361 void C4FileSelDlg::AddCheckedLocation(const char *szName, const char *szPath)
362 {
363 	// check location
364 	// path must exit
365 	if (!szPath || !*szPath) return;
366 	if (!DirectoryExists(szPath)) return;
367 	// path must not be in list yet
368 	for (int32_t i=0; i<iLocationCount; ++i)
369 		if (ItemIdentical(szPath, pLocations[i].sPath.getData()))
370 			return;
371 	// OK; add it!
372 	AddLocation(szName, szPath);
373 }
374 
GetCurrentLocationIndex() const375 int32_t C4FileSelDlg::GetCurrentLocationIndex() const
376 {
377 	return iCurrentLocationIndex;
378 }
379 
SetCurrentLocation(int32_t idx,bool fRefresh)380 void C4FileSelDlg::SetCurrentLocation(int32_t idx, bool fRefresh)
381 {
382 	// safety
383 	if (!Inside<int32_t>(idx, 0,iLocationCount)) return;
384 	// update ComboBox-text
385 	iCurrentLocationIndex = idx;
386 	if (pLocationComboBox) pLocationComboBox->SetText(pLocations[idx].sName.getData());
387 	// set new path
388 	SetPath(pLocations[idx].sPath.getData(), fRefresh);
389 }
390 
391 
392 // ---------------------------------------------------
393 // C4PlayerSelDlg
394 
C4PlayerSelDlg(C4FileSel_BaseCB * pSelCallback)395 C4PlayerSelDlg::C4PlayerSelDlg(C4FileSel_BaseCB *pSelCallback)
396 		: C4FileSelDlg(Config.General.UserDataPath, LoadResStr("IDS_MSG_SELECTPLR"), pSelCallback)
397 {
398 }
399 
400 
401 // ---------------------------------------------------
402 // C4DefinitionSelDlg
403 
C4DefinitionSelDlg(C4FileSel_BaseCB * pSelCallback,const char * szFixedSelection)404 C4DefinitionSelDlg::C4DefinitionSelDlg(C4FileSel_BaseCB *pSelCallback, const char *szFixedSelection)
405 		: C4FileSelDlg(Config.General.UserDataPath, FormatString(LoadResStr("IDS_MSG_SELECT"), LoadResStr("IDS_DLG_DEFINITIONS")).getData(), pSelCallback)
406 {
407 	if (szFixedSelection) sFixedSelection.Copy(szFixedSelection);
408 }
409 
OnShown()410 void C4DefinitionSelDlg::OnShown()
411 {
412 	// base call: load file list
413 	C4FileSelDlg::OnShown();
414 	// initial selection
415 	if (sFixedSelection) SetSelection(sFixedSelection.getData(), true);
416 }
417 
IsItemGrayed(const char * szFilename) const418 bool C4DefinitionSelDlg::IsItemGrayed(const char *szFilename) const
419 {
420 	// cannot change initial selection
421 	if (!sFixedSelection) return false;
422 	return SIsModule(sFixedSelection.getData(), GetFilename(szFilename));
423 }
424 
SelectDefinitions(C4GUI::Screen * pOnScreen,StdStrBuf * pSelection)425 bool C4DefinitionSelDlg::SelectDefinitions(C4GUI::Screen *pOnScreen, StdStrBuf *pSelection)
426 {
427 	// let the user select definitions by showing a modal selection dialog
428 	C4DefinitionSelDlg *pDlg = new C4DefinitionSelDlg(nullptr, pSelection->getData());
429 	bool fResult;
430 	if ((fResult = pOnScreen->ShowModalDlg(pDlg, false)))
431 	{
432 		pSelection->Copy(pDlg->GetSelection(pSelection->getData(), true));
433 	}
434 	delete pDlg;
435 	return fResult;
436 }
437 
438 
439 
440 // ---------------------------------------------------
441 // C4PortraitSelDlg::ListItem
442 
ListItem(const char * szFilename)443 C4PortraitSelDlg::ListItem::ListItem(const char *szFilename) : C4FileSelDlg::ListItem(szFilename)
444 		, fError(false), fLoaded(false)
445 {
446 	CStdFont *pUseFont = &(::GraphicsResource.MiniFont);
447 	// determine label text
448 	StdStrBuf sDisplayLabel;
449 	if (szFilename)
450 	{
451 		sDisplayLabel.Copy(::GetFilename(szFilename));
452 		::RemoveExtension(&sDisplayLabel);
453 	}
454 	else
455 	{
456 		sDisplayLabel.Ref(LoadResStr("IDS_MSG_NOPORTRAIT"));
457 	}
458 	// insert linebreaks into label text
459 	int32_t iLineHgt = std::max<int32_t>(pUseFont->BreakMessage(sDisplayLabel.getData(), ImagePreviewSize-6, &sFilenameLabelText, false), 1);
460 	// set size
461 	SetBounds(C4Rect(0,0,ImagePreviewSize,ImagePreviewSize+iLineHgt));
462 }
463 
Load()464 void C4PortraitSelDlg::ListItem::Load()
465 {
466 	if (sFilename)
467 	{
468 		// safety
469 		fLoaded = false;
470 		// load image file
471 		C4Group SrcGrp;
472 		StdStrBuf sParentPath;
473 		GetParentPath(sFilename.getData(), &sParentPath);
474 		bool fLoadError = true;
475 		if (SrcGrp.Open(sParentPath.getData()))
476 			if (fctLoadedImage.Load(SrcGrp, ::GetFilename(sFilename.getData()), C4FCT_Full, C4FCT_Full, false, 0))
477 			{
478 				// image loaded. Can only be put into facet by main thread, because those operations aren't thread safe
479 				fLoaded = true;
480 				fLoadError = false;
481 			}
482 		SrcGrp.Close();
483 		fError = fLoadError;
484 	}
485 }
486 
DrawElement(C4TargetFacet & cgo)487 void C4PortraitSelDlg::ListItem::DrawElement(C4TargetFacet &cgo)
488 {
489 	// Scale down newly loaded image?
490 	if (fLoaded)
491 	{
492 		fLoaded = false;
493 		if (!fctImage.CopyFromSfcMaxSize(fctLoadedImage.GetFace(), ImagePreviewSize))
494 			fError = true;
495 		fctLoadedImage.GetFace().Clear();
496 		fctLoadedImage.Clear();
497 	}
498 	// Draw picture
499 	CStdFont *pUseFont = &(::GraphicsResource.MiniFont);
500 	C4Facet cgoPicture(cgo.Surface, cgo.TargetX+rcBounds.x, cgo.TargetY+rcBounds.y, ImagePreviewSize, ImagePreviewSize);
501 	if (fError || !sFilename)
502 	{
503 		C4Facet &fctNoneImg = ::GraphicsResource.fctOKCancel;
504 		fctNoneImg.Draw(cgoPicture.Surface, cgoPicture.X+(cgoPicture.Wdt-fctNoneImg.Wdt)/2, cgoPicture.Y+(cgoPicture.Hgt-fctNoneImg.Hgt)/2, 1,0);
505 	}
506 	else
507 	{
508 		if (!fctImage.Surface)
509 		{
510 			// not loaded yet
511 			pDraw->TextOut(LoadResStr("IDS_PRC_INITIALIZE"), ::GraphicsResource.MiniFont, 1.0f, cgo.Surface, cgoPicture.X+cgoPicture.Wdt/2, cgoPicture.Y+(cgoPicture.Hgt-::GraphicsResource.MiniFont.GetLineHeight())/2, C4GUI_StatusFontClr, ACenter, false);
512 		}
513 		else
514 		{
515 			fctImage.Draw(cgoPicture);
516 		}
517 	}
518 	// draw filename
519 	pDraw->TextOut(sFilenameLabelText.getData(), *pUseFont, 1.0f, cgo.Surface, cgoPicture.X+rcBounds.Wdt/2, cgoPicture.Y+cgoPicture.Hgt, C4GUI_MessageFontClr, ACenter, false);
520 }
521 
522 
523 // ---------------------------------------------------
524 // C4PortraitSelDlg::LoaderThread
525 
ClearLoadItems()526 void C4PortraitSelDlg::LoaderThread::ClearLoadItems()
527 {
528 	// stop thread so list can be accessed
529 	Stop();
530 	// clear list
531 	LoadItems.clear();
532 }
533 
AddLoadItem(ListItem * pItem)534 void C4PortraitSelDlg::LoaderThread::AddLoadItem(ListItem *pItem)
535 {
536 	// not to be called when thread is running!
537 	assert(!IsStarted());
538 	LoadItems.push_back(pItem);
539 }
540 
Execute()541 void C4PortraitSelDlg::LoaderThread::Execute()
542 {
543 	// list empty?
544 	if (!LoadItems.size())
545 	{
546 		// then we're done!
547 		SignalStop();
548 		return;
549 	}
550 	// load one item at the time
551 	ListItem *pLoadItem = LoadItems.front();
552 	pLoadItem->Load();
553 	LoadItems.erase(LoadItems.begin());
554 }
555 
556 // ---------------------------------------------------
557 // C4PortraitSelDlg
558 
C4PortraitSelDlg(C4FileSel_BaseCB * pSelCallback)559 C4PortraitSelDlg::C4PortraitSelDlg(C4FileSel_BaseCB *pSelCallback)
560 		: C4FileSelDlg(Config.General.SystemDataPath, FormatString(LoadResStr("IDS_MSG_SELECT"), LoadResStr("IDS_TYPE_PORTRAIT")).getData(), pSelCallback, false)
561 {
562 	char path[_MAX_PATH+1];
563 	// add common picture locations
564 	StdStrBuf strLocation;
565 	SCopy(Config.General.UserDataPath, path, _MAX_PATH); TruncateBackslash(path);
566 	strLocation.Format("%s %s", C4ENGINECAPTION, LoadResStr("IDS_TEXT_USERPATH"));
567 	AddLocation(strLocation.getData(), path);
568 	SCopy(Config.General.SystemDataPath, path, _MAX_PATH); TruncateBackslash(path);
569 	strLocation.Format("%s %s", C4ENGINECAPTION, LoadResStr("IDS_TEXT_PROGRAMDIRECTORY"));
570 	AddCheckedLocation(strLocation.getData(), path);
571 #ifdef _WIN32
572 	wchar_t wpath[MAX_PATH+1];
573 	if (SHGetSpecialFolderPathW(nullptr, wpath, CSIDL_PERSONAL, false)) AddCheckedLocation(LoadResStr("IDS_TEXT_MYDOCUMENTS"), StdStrBuf(wpath).getData());
574 	if (SHGetSpecialFolderPathW(nullptr, wpath, CSIDL_MYPICTURES, false)) AddCheckedLocation(LoadResStr("IDS_TEXT_MYPICTURES"), StdStrBuf(wpath).getData());
575 	if (SHGetSpecialFolderPathW(nullptr, wpath, CSIDL_DESKTOPDIRECTORY, false)) AddCheckedLocation(LoadResStr("IDS_TEXT_DESKTOP"), StdStrBuf(wpath).getData());
576 #endif
577 #ifdef __APPLE__
578 	AddCheckedLocation(LoadResStr("IDS_TEXT_HOME"), getenv("HOME"));
579 #else
580 	AddCheckedLocation(LoadResStr("IDS_TEXT_HOMEFOLDER"), getenv("HOME"));
581 #endif
582 #ifndef _WIN32
583 	sprintf(path, "%s%c%s", getenv("HOME"), (char)DirectorySeparator, (const char *)"Desktop");
584 	AddCheckedLocation(LoadResStr("IDS_TEXT_DESKTOP"), path);
585 #endif
586 	// build dialog
587 	InitElements();
588 	// select last location
589 	SetCurrentLocation(Config.Startup.LastPortraitFolderIdx, false);
590 }
591 
OnClosed(bool fOK)592 void C4PortraitSelDlg::OnClosed(bool fOK)
593 {
594 	// remember location
595 	Config.Startup.LastPortraitFolderIdx = GetCurrentLocationIndex();
596 	// inherited
597 	C4FileSelDlg::OnClosed(fOK);
598 }
599 
CreateListItem(const char * szFilename)600 C4FileSelDlg::ListItem *C4PortraitSelDlg::CreateListItem(const char *szFilename)
601 {
602 	// use own list item type
603 	ListItem *pNew = new ListItem(szFilename);;
604 	// schedule image loading
605 	ImageLoader.AddLoadItem(pNew);
606 	return pNew;
607 }
608 
BeginFileListUpdate()609 void C4PortraitSelDlg::BeginFileListUpdate()
610 {
611 	// new file list. Stop loading current
612 	ImageLoader.ClearLoadItems();
613 }
614 
EndFileListUpdate()615 void C4PortraitSelDlg::EndFileListUpdate()
616 {
617 #if USE_BACKGROUND_THREAD_LOAD
618 	// Begin loading images
619 	ImageLoader.Start();
620 #endif
621 }
622 
OnIdle()623 void C4PortraitSelDlg::OnIdle()
624 {
625 #if !USE_BACKGROUND_THREAD_LOAD
626 	// no multithreading? Workaround for image loading then...
627 	static int32_t i = 0;
628 	if (!(i++%10)) ImageLoader.Execute();
629 #endif
630 }
631 
SelectPortrait(C4GUI::Screen * pOnScreen,StdStrBuf * pSelection)632 bool C4PortraitSelDlg::SelectPortrait(C4GUI::Screen *pOnScreen, StdStrBuf *pSelection)
633 {
634 	// let the user select a portrait by showing a modal selection dialog
635 	C4PortraitSelDlg *pDlg = new C4PortraitSelDlg(nullptr);
636 	bool fResult;
637 	if ((fResult = pOnScreen->ShowModalDlg(pDlg, false)))
638 	{
639 		pSelection->Take(pDlg->GetSelection(nullptr, false));
640 	}
641 	delete pDlg;
642 	return fResult;
643 }
644