1 #include "Button_System.h"
2 #include "Directories.h"
3 #include "FileMan.h"
4 #include "Font.h"
5 #include "Font_Control.h"
6 #include "HImage.h"
7 #include "Local.h"
8 #include "RenderWorld.h"
9 #include "Render_Dirty.h"
10 #include "LoadScreen.h"
11 #include "SelectWin.h"
12 #include "EditorDefines.h"
13 #include "MessageBox.h"
14 #include "Text_Input.h"
15 #include "Soldier_Create.h"
16 #include "Soldier_Init_List.h"
17 #include "EditorBuildings.h"
18 #include "Editor_Taskbar_Utils.h"
19 #include "Editor_Undo.h"
20 #include "EditScreen.h"
21 #include "StrategicMap.h"
22 #include "Editor_Modes.h"
23 #include "Map_Information.h"
24 #include "Sys_Globals.h"
25 #include "Sector_Summary.h"
26 #include "NewSmooth.h"
27 #include "Simple_Render_Utils.h"
28 #include "Animated_ProgressBar.h"
29 #include "EditorMercs.h"
30 #include "Lighting.h"
31 #include "EditorMapInfo.h"
32 #include "Environment.h"
33 #include "Edit_Sys.h"
34 #include "EditorItems.h"
35 #include "English.h"
36 #include "GameLoop.h"
37 #include "Message.h"
38 #include "Pits.h"
39 #include "Item_Statistics.h"
40 #include "Scheduling.h"
41 #include "Debug.h"
42 #include "JAScreens.h"
43 #include "MemMan.h"
44 #include "MessageBoxScreen.h"
45 #include "Timer_Control.h"
46 #include "VObject.h"
47 #include "VSurface.h"
48 #include "Video.h"
49 #include "WorldDef.h"
50 #include "UILayout.h"
51 #include "GameState.h"
52 #include "GameRes.h"
53 
54 #include "ContentManager.h"
55 #include "GameInstance.h"
56 
57 #include <string_theory/format>
58 #include <string_theory/string>
59 
60 #include <cstdarg>
61 
62 
63 static BOOLEAN gfErrorCatch            = FALSE;
64 static ST::string gzErrorCatchString;
65 
66 
SetErrorCatchString(const ST::string & str)67 void SetErrorCatchString(const ST::string& str)
68 {
69 	gzErrorCatchString = str;
70 	gfErrorCatch = TRUE;
71 }
72 
73 
74 enum{
75 	DIALOG_NONE,
76 	DIALOG_SAVE,
77 	DIALOG_LOAD,
78 	DIALOG_CANCEL,
79 	DIALOG_DELETE
80 };
81 
82 static INT32 iTotalFiles;
83 static INT32 iTopFileShown;
84 static INT32 iCurrFileShown;
85 static INT32	iLastFileClicked;
86 static INT32 iLastClickTime;
87 
88 static ST::string gzFilename;
89 
90 static FDLG_LIST* FileList = NULL;
91 
92 static INT32 iFDlgState = DIALOG_NONE;
93 static GUIButtonRef iFileDlgButtons[7];
94 
95 static BOOLEAN gfLoadError;
96 static bool gfReadOnly;
97 static BOOLEAN gfFileExists;
98 static BOOLEAN gfIllegalName;
99 static BOOLEAN gfDeleteFile;
100 static BOOLEAN gfNoFiles;
101 
102 static BOOLEAN fEnteringLoadSaveScreen = TRUE;
103 
104 static MOUSE_REGION BlanketRegion;
105 
106 static ST::string gMapFileForRemoval;
107 
108 enum{
109 	IOSTATUS_NONE,
110 	INITIATE_MAP_SAVE,
111 	SAVING_MAP,
112 	INITIATE_MAP_LOAD,
113 	LOADING_MAP
114 };
115 static INT8 gbCurrentFileIOStatus; // 1 init saving message, 2 save, 3 init loading message, 4 load, 0 none
116 
117 static bool gfUpdateSummaryInfo = true;
118 
119 
120 static void CreateFileDialog(const ST::string& zTitle);
121 static void TrashFDlgList(FDLG_LIST*);
122 
123 
LoadSaveScreenEntry(void)124 static void LoadSaveScreenEntry(void)
125 {
126 	fEnteringLoadSaveScreen = FALSE;
127 	gbCurrentFileIOStatus	= IOSTATUS_NONE;
128 
129 	gfReadOnly = false;
130 	gfFileExists = FALSE;
131 	gfLoadError = FALSE;
132 	gfIllegalName = FALSE;
133 	gfDeleteFile = FALSE;
134 	gfNoFiles = FALSE;
135 
136 	// setup filename dialog box
137 	// (*.dat and *.map) as file filter
138 
139 	// If user clicks on a filename in the window, then turn off string focus and re-init the string with the new name.
140 	// If user hits ENTER or presses OK, then continue with the file loading/saving
141 
142 	if( FileList )
143 		TrashFDlgList( FileList );
144 
145 	iTopFileShown = iTotalFiles = 0;
146 	try
147 	{
148 		std::vector<ST::string> files = GCM->getAllMaps();
149 		for (const ST::string &file : files)
150 		{
151 			FileList = AddToFDlgList(FileList, file.c_str());
152 			++iTotalFiles;
153 		}
154 	}
155 	catch (...) { /* XXX ignore */ }
156 
157 	gzFilename = ST::format("{}", g_filename);
158 
159 	CreateFileDialog(iCurrentAction == ACTION_SAVE_MAP ? "Save Map (*.dat)" : "Load Map (*.dat)");
160 
161 	if( !iTotalFiles )
162 	{
163 		gfNoFiles = TRUE;
164 		if( iCurrentAction == ACTION_LOAD_MAP )
165 			DisableButton( iFileDlgButtons[0] );
166 	}
167 
168 	iLastFileClicked = -1;
169 	iLastClickTime = 0;
170 
171 }
172 
173 
174 static void RemoveFileDialog(void);
175 static void RemoveFromFDlgList(FDLG_LIST** head, FDLG_LIST* node);
176 static BOOLEAN ValidFilename(void);
177 
178 
ProcessLoadSaveScreenMessageBoxResult(void)179 static ScreenID ProcessLoadSaveScreenMessageBoxResult(void)
180 {
181 	FDLG_LIST *curr, *temp;
182 	gfRenderWorld = TRUE;
183 	RemoveMessageBox();
184 	if( gfIllegalName )
185 	{
186 		fEnteringLoadSaveScreen = TRUE;
187 		RemoveFileDialog();
188 		MarkWorldDirty();
189 		return gfMessageBoxResult ? LOADSAVE_SCREEN : EDIT_SCREEN;
190 	}
191 	if( gfDeleteFile )
192 	{
193 		if( gfMessageBoxResult )
194 		{ //delete file
195 			INT32 x;
196 			curr = FileList;
197 			for( x = 0; x < iCurrFileShown && x < iTotalFiles && curr; x++ )
198 			{
199 				curr = curr->pNext;
200 			}
201 			if( curr )
202 			{
203 				FileDelete(gMapFileForRemoval);
204 
205 				//File is deleted so redo the text fields so they show the
206 				//next file in the list.
207 				temp = curr->pNext;
208 				if( !temp )
209 					temp = curr->pPrev;
210 				if( !temp )
211 					gzFilename = ST::null;
212 				else
213 					gzFilename = ST::format("{}", temp->filename);
214 				if (!ValidFilename()) gzFilename = ST::null;
215 				SetInputFieldString(0, gzFilename);
216 				RemoveFromFDlgList( &FileList, curr );
217 				iTotalFiles--;
218 				if( !iTotalFiles )
219 				{
220 					gfNoFiles = TRUE;
221 					if( iCurrentAction == ACTION_LOAD_MAP )
222 						DisableButton( iFileDlgButtons[0] );
223 				}
224 				if( iCurrFileShown >= iTotalFiles )
225 					iCurrFileShown--;
226 				if( iCurrFileShown < iTopFileShown )
227 					iTopFileShown -= 8;
228 				if( iTopFileShown < 0 )
229 					iTopFileShown = 0;
230 			}
231 		}
232 		MarkWorldDirty();
233 		RenderWorld();
234 		gfDeleteFile = FALSE;
235 		iFDlgState = DIALOG_NONE;
236 		return LOADSAVE_SCREEN;
237 	}
238 	if( gfLoadError )
239 	{
240 		fEnteringLoadSaveScreen = TRUE;
241 		return gfMessageBoxResult ? LOADSAVE_SCREEN : EDIT_SCREEN;
242 	}
243 	if( gfReadOnly )
244 	{ //file is readonly.  Result will determine if the file dialog stays up.
245 		fEnteringLoadSaveScreen = TRUE;
246 		RemoveFileDialog();
247 		return gfMessageBoxResult ? LOADSAVE_SCREEN : EDIT_SCREEN;
248 	}
249 	if( gfFileExists )
250 	{
251 		if( gfMessageBoxResult )
252 		{ //okay to overwrite file
253 			RemoveFileDialog();
254 			gbCurrentFileIOStatus = INITIATE_MAP_SAVE;
255 			return LOADSAVE_SCREEN;
256 		}
257 		fEnteringLoadSaveScreen = TRUE;
258 		RemoveFileDialog();
259 		return EDIT_SCREEN ;
260 	}
261 	SLOGA("ProcessLoadSaveScreenMessageBoxResult: none of the global flags set");
262 	return LOADSAVE_SCREEN;
263 }
264 
265 
266 static void DrawFileDialog(void);
267 static BOOLEAN ExtractFilenameFromFields(void);
268 static void HandleMainKeyEvents(InputAtom* pEvent);
269 static ScreenID ProcessFileIO(void);
270 
271 
LoadSaveScreenHandle(void)272 ScreenID LoadSaveScreenHandle(void)
273 {
274 	FDLG_LIST *FListNode;
275 	INT32 x;
276 	InputAtom DialogEvent;
277 	ST::string zOrigName;
278 
279 	if( fEnteringLoadSaveScreen )
280 	{
281 		LoadSaveScreenEntry();
282 	}
283 
284 	if( gbCurrentFileIOStatus ) //loading or saving map
285 	{
286 		ScreenID const uiScreen = ProcessFileIO();
287 		if( uiScreen == EDIT_SCREEN && gbCurrentFileIOStatus == LOADING_MAP )
288 			RemoveProgressBar( 0 );
289 		return uiScreen;
290 	}
291 
292 	if( gubMessageBoxStatus )
293 	{
294 		if( MessageBoxHandled() )
295 			return ProcessLoadSaveScreenMessageBoxResult();
296 		return LOADSAVE_SCREEN;
297 	}
298 
299 	//handle all key input.
300 	while( DequeueEvent(&DialogEvent) )
301 	{
302 		if( !HandleTextInput(&DialogEvent) && (DialogEvent.usEvent == KEY_DOWN || DialogEvent.usEvent == KEY_REPEAT) )
303 		{
304 			HandleMainKeyEvents( &DialogEvent );
305 		}
306 	}
307 
308 	DrawFileDialog();
309 
310 	// Skip to first filename to show
311 	FListNode = FileList;
312 	for(x=0;x<iTopFileShown && x<iTotalFiles && FListNode != NULL;x++)
313 	{
314 		FListNode = FListNode->pNext;
315 	}
316 
317 	// Show up to 8 filenames in the window
318 	SetFont( FONT12POINT1 );
319 	if( gfNoFiles )
320 	{
321 		SetFontForeground( FONT_LTRED );
322 		SetFontBackground( 142 );
323 		MPrint(226, 126, "NO FILES IN /MAPS DIRECTORY");
324 	}
325 	else for(x=iTopFileShown;x<(iTopFileShown+8) && x<iTotalFiles && FListNode != NULL; x++)
326 	{
327 		if( !EditingText() && x == iCurrFileShown  )
328 		{
329 			SetFontForeground( FONT_GRAY2 );
330 			SetFontBackground( FONT_METALGRAY );
331 		}
332 		else
333 		{
334 			SetFontForeground( FONT_BLACK );
335 			SetFontBackground( 142 );
336 		}
337 		MPrint(186, 73 + (x - iTopFileShown) * 15, ST::format("{}", FListNode->filename));
338 		FListNode = FListNode->pNext;
339 	}
340 
341 	RenderAllTextFields();
342 
343 	InvalidateScreen();
344 
345 	ExecuteBaseDirtyRectQueue();
346 
347 	switch( iFDlgState )
348 	{
349 		case DIALOG_CANCEL:
350 			RemoveFileDialog();
351 			fEnteringLoadSaveScreen = TRUE;
352 			return EDIT_SCREEN;
353 
354 		case DIALOG_DELETE:
355 		{
356 			gMapFileForRemoval = GCM->getMapPath(gzFilename);
357 			bool readonly = false;
358 			if (Fs_getReadOnly(gMapFileForRemoval.c_str(), &readonly))
359 			{
360 				ST::string str;
361 				if (readonly)
362 				{
363 					str = ST::format(" Delete READ-ONLY file {}? ", gzFilename);
364 				}
365 				else
366 					str = ST::format(" Delete file {}? ", gzFilename);
367 				gfDeleteFile = TRUE;
368 				CreateMessageBox( str );
369 			}
370 			return LOADSAVE_SCREEN;
371 		}
372 
373 		case DIALOG_SAVE:
374 		{
375 			if( !ExtractFilenameFromFields() )
376 			{
377 				CreateMessageBox( " Illegal filename.  Try another filename? " );
378 				gfIllegalName = TRUE;
379 				iFDlgState = DIALOG_NONE;
380 				return LOADSAVE_SCREEN;
381 			}
382 			ST::string filename(GCM->getMapPath(gzFilename));
383 			if ( GCM->doesGameResExists(filename.c_str()) )
384 			{
385 				gfFileExists = TRUE;
386 				gfReadOnly = false;
387 				Fs_getReadOnly(filename.c_str(), &gfReadOnly);
388 				if( gfReadOnly )
389 					CreateMessageBox( " File is read only!  Choose a different name? " );
390 				else
391 					CreateMessageBox( " File exists, Overwrite? " );
392 				return( LOADSAVE_SCREEN );
393 			}
394 			RemoveFileDialog();
395 			gbCurrentFileIOStatus = INITIATE_MAP_SAVE;
396 			return LOADSAVE_SCREEN ;
397 		}
398 		case DIALOG_LOAD:
399 			if( !ExtractFilenameFromFields() )
400 			{
401 				CreateMessageBox( " Illegal filename.  Try another filename? " );
402 				gfIllegalName = TRUE;
403 				iFDlgState = DIALOG_NONE;
404 				return LOADSAVE_SCREEN;
405 			}
406 			RemoveFileDialog();
407 			CreateProgressBar(0, 118, 183, 404, 19);
408 			DefineProgressBarPanel( 0, 65, 79, 94, 100, 155, 540, 235 );
409 			zOrigName = ST::format("Loading map:  {}", gzFilename);
410 			SetProgressBarTitle( 0, zOrigName, BLOCKFONT2, FONT_RED, FONT_NEARBLACK );
411 			gbCurrentFileIOStatus = INITIATE_MAP_LOAD;
412 			return LOADSAVE_SCREEN ;
413 		default:
414 			iFDlgState = DIALOG_NONE;
415 	}
416 	iFDlgState = DIALOG_NONE;
417 	return LOADSAVE_SCREEN ;
418 }
419 
420 
MakeButtonArrow(const char * const gfx,const INT16 y,const GUI_CALLBACK click)421 static GUIButtonRef MakeButtonArrow(const char* const gfx, const INT16 y, const GUI_CALLBACK click)
422 {
423 	GUIButtonRef const btn = QuickCreateButtonImg(gfx, -1, 1, 2, 3, 4, 426, y, MSYS_PRIORITY_HIGH, click);
424 	btn->SpecifyDisabledStyle(GUI_BUTTON::DISABLED_STYLE_SHADED);
425 	return btn;
426 }
427 
428 
429 static void FDlgCancelCallback(GUI_BUTTON* butn, INT32 reason);
430 static void FDlgDwnCallback(GUI_BUTTON* butn, INT32 reason);
431 static void FDlgNamesCallback(GUI_BUTTON* butn, INT32 reason);
432 static void FDlgOkCallback(GUI_BUTTON* butn, INT32 reason);
433 static void FDlgUpCallback(GUI_BUTTON* butn, INT32 reason);
434 static void FileDialogModeCallback(UINT8 ubID, BOOLEAN fEntering);
435 static void UpdateWorldInfoCallback(GUI_BUTTON* b, INT32 reason);
436 
437 
CreateFileDialog(const ST::string & zTitle)438 static void CreateFileDialog(const ST::string& zTitle)
439 {
440 
441 	iFDlgState = DIALOG_NONE;
442 
443 	DisableEditorTaskbar();
444 
445 	MSYS_DefineRegion( &BlanketRegion, 0, 0, gsVIEWPORT_END_X, gsVIEWPORT_END_Y, MSYS_PRIORITY_HIGH - 5, 0, 0, 0 );
446 
447 	//Okay and cancel buttons
448 	iFileDlgButtons[0] = CreateTextButton("Okay",   FONT12POINT1, FONT_BLACK, FONT_BLACK, 354, 225, 50, 30, MSYS_PRIORITY_HIGH, FDlgOkCallback);
449 	iFileDlgButtons[1] = CreateTextButton("Cancel", FONT12POINT1, FONT_BLACK, FONT_BLACK, 406, 225, 50, 30, MSYS_PRIORITY_HIGH, FDlgCancelCallback);
450 
451 	//Scroll buttons
452 	iFileDlgButtons[2] = MakeButtonArrow(EDITORDIR "/uparrow.sti",    92, FDlgUpCallback);
453 	iFileDlgButtons[3] = MakeButtonArrow(EDITORDIR "/downarrow.sti", 182, FDlgDwnCallback);
454 
455 	//File list window
456 	iFileDlgButtons[4] = CreateHotSpot(179 + 4, 69 + 3, 179 + 4 + 240, 69 + 120 + 3, MSYS_PRIORITY_HIGH - 1, FDlgNamesCallback);
457 	//Title button
458 	iFileDlgButtons[5] = CreateLabel(zTitle, HUGEFONT, FONT_LTKHAKI, FONT_DKKHAKI, 179, 39, 281, 30, MSYS_PRIORITY_HIGH - 2);
459 
460 	if( iCurrentAction == ACTION_SAVE_MAP )
461 	{	//checkboxes
462 		//The update world info checkbox
463 		iFileDlgButtons[6] = CreateCheckBoxButton( 183, 229, EDITORDIR "/smcheckbox.sti", MSYS_PRIORITY_HIGH, UpdateWorldInfoCallback );
464 		if( gfUpdateSummaryInfo )
465 			iFileDlgButtons[6]->uiFlags |= BUTTON_CLICKED_ON;
466 	}
467 
468 	//Add the text input fields
469 	InitTextInputModeWithScheme( DEFAULT_SCHEME );
470 	//field 1 (filename)
471 	AddTextInputField(/*233*/183, 195, 190, 20, MSYS_PRIORITY_HIGH, gzFilename, 30, INPUTTYPE_DOSFILENAME);
472 	//field 2 -- user field that allows mouse/key interaction with the filename list
473 	AddUserInputField( FileDialogModeCallback );
474 }
475 
476 
UpdateWorldInfoCallback(GUI_BUTTON * b,INT32 reason)477 static void UpdateWorldInfoCallback(GUI_BUTTON* b, INT32 reason)
478 {
479 	if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
480 		gfUpdateSummaryInfo = b->Clicked();
481 }
482 
483 
484 //This is a hook into the text input code.  This callback is called whenever the user is currently
485 //editing text, and presses Tab to transfer to the file dialog mode.  When this happens, we set the text
486 //field to the currently selected file in the list which is already know.
FileDialogModeCallback(UINT8 ubID,BOOLEAN fEntering)487 static void FileDialogModeCallback(UINT8 ubID, BOOLEAN fEntering)
488 {
489 	INT32 x;
490 	FDLG_LIST *FListNode;
491 	if( fEntering )
492 	{
493 		// Skip to first filename
494 		FListNode = FileList;
495 		for(x=0;x<iTopFileShown && x<iTotalFiles && FListNode != NULL;x++)
496 		{
497 			FListNode = FListNode->pNext;
498 		}
499 		// Find the already selected filename
500 		for(x = iTopFileShown; x < iTopFileShown + 8 && x < iTotalFiles && FListNode != NULL; x++ )
501 		{
502 			if( iCurrFileShown == (x-iTopFileShown) )
503 			{
504 				FListNode->filename[30] = '\0';
505 				SetInputFieldString(0, FListNode->filename);
506 				return;
507 			}
508 			FListNode = FListNode->pNext;
509 		}
510 	}
511 }
512 
513 
RemoveFileDialog(void)514 static void RemoveFileDialog(void)
515 {
516 	INT32 x;
517 
518 	MSYS_RemoveRegion( &BlanketRegion );
519 
520 	for(x=0; x<6; x++)
521 	{
522 		RemoveButton(iFileDlgButtons[x]);
523 	}
524 
525 	if (iFileDlgButtons[6]) RemoveButton(iFileDlgButtons[6]);
526 
527 	TrashFDlgList( FileList );
528 	FileList = NULL;
529 
530 	InvalidateScreen( );
531 
532 	EnableEditorTaskbar();
533 	KillTextInputMode();
534 	MarkWorldDirty();
535 	RenderWorld();
536 }
537 
538 
DrawFileDialog(void)539 static void DrawFileDialog(void)
540 {
541 	ColorFillVideoSurfaceArea(FRAME_BUFFER,	179, 69, (179+281), 261, Get16BPPColor(FROMRGB(136, 138, 135)) );
542 	ColorFillVideoSurfaceArea(FRAME_BUFFER,	180, 70, (179+281), 261, Get16BPPColor(FROMRGB(24, 61, 81)) );
543 	ColorFillVideoSurfaceArea(FRAME_BUFFER,	180, 70, (179+280), 260, Get16BPPColor(FROMRGB(65, 79, 94)) );
544 
545 	ColorFillVideoSurfaceArea(FRAME_BUFFER, (179+4), (69+3), (179+4+240), (69+123), Get16BPPColor(FROMRGB(24, 61, 81)) );
546 	ColorFillVideoSurfaceArea(FRAME_BUFFER, (179+5), (69+4), (179+4+240), (69+123), Get16BPPColor(FROMRGB(136, 138, 135)) );
547 	ColorFillVideoSurfaceArea(FRAME_BUFFER, (179+5), (69+4), (179+3+240), (69+122), Get16BPPColor(FROMRGB(250, 240, 188)) );
548 
549 	MarkButtonsDirty();
550 	RenderButtons();
551 	RenderButtonsFastHelp();
552 
553 	SetFontAttributes(FONT10ARIAL, FONT_LTKHAKI, FONT_DKKHAKI);
554 	MPrint(183, 217, "Filename");
555 
556 	if (iFileDlgButtons[6]) MPrint(200, 231, "Update world info");
557 }
558 
559 
560 //The callback calls this function passing the relative y position of where
561 //the user clicked on the hot spot.
SelectFileDialogYPos(UINT16 usRelativeYPos)562 static void SelectFileDialogYPos(UINT16 usRelativeYPos)
563 {
564 	INT16 sSelName;
565 	INT32 x;
566 	FDLG_LIST *FListNode;
567 
568 	sSelName = usRelativeYPos / 15;
569 
570 	//This is a field in the text editmode, but clicked via mouse.
571 	SetActiveField( 1 );
572 
573 	// Skip to first filename
574 	FListNode = FileList;
575 	for(x=0;x<iTopFileShown && x<iTotalFiles && FListNode != NULL;x++)
576 	{
577 		FListNode = FListNode->pNext;
578 	}
579 
580 	for(x=iTopFileShown;x<(iTopFileShown+8) && x<iTotalFiles && FListNode != NULL; x++)
581 	{
582 		if( (INT32)sSelName == (x-iTopFileShown) )
583 		{
584 			INT32 iCurrClickTime;
585 			iCurrFileShown = x;
586 			FListNode->filename[30] = '\0';
587 			gzFilename = ST::format("{}", FListNode->filename);
588 			if (!ValidFilename()) gzFilename = ST::null;
589 			SetInputFieldString(0, gzFilename);
590 
591 			RenderInactiveTextField( 0 );
592 
593 			//Calculate and process any double clicking...
594 			iCurrClickTime = GetJA2Clock();
595 			if( iCurrClickTime - iLastClickTime < 400 && x == iLastFileClicked )
596 			{ //Considered a double click, so activate load/save this filename.
597 				iFDlgState = iCurrentAction == ACTION_SAVE_MAP ? DIALOG_SAVE : DIALOG_LOAD;
598 			}
599 			iLastClickTime = iCurrClickTime;
600 			iLastFileClicked = x;
601 		}
602 		FListNode = FListNode->pNext;
603 	}
604 }
605 
606 
AddToFDlgList(FDLG_LIST * const list,char const * const filename)607 FDLG_LIST* AddToFDlgList(FDLG_LIST* const list, char const* const filename)
608 { // Add and sort alphabetically without regard to case
609 	FDLG_LIST* prev = 0;
610 	FDLG_LIST* i;
611 	for (i = list; i; prev = i, i = i->pNext)
612 	{
613 		if (strcasecmp(i->filename, filename) > 0) break;
614 	}
615 	FDLG_LIST* const n = new FDLG_LIST{};
616 	strlcpy(n->filename, filename, lengthof(n->filename));
617 	n->pPrev = prev;
618 	n->pNext = i;
619 	if (i)    i->pPrev    = n;
620 	if (prev) prev->pNext = n;
621 	return prev ? list : n;
622 }
623 
624 
RemoveFromFDlgList(FDLG_LIST ** const head,FDLG_LIST * const node)625 static void RemoveFromFDlgList(FDLG_LIST** const head, FDLG_LIST* const node)
626 {
627 	for (FDLG_LIST* i = *head; i; i = i->pNext)
628 	{
629 		if (i != node) continue;
630 		if (*head == node) *head = (*head)->pNext;
631 		FDLG_LIST* const prev = i->pPrev;
632 		FDLG_LIST* const next = i->pNext;
633 		if (prev) prev->pNext = next;
634 		if (next) next->pPrev = prev;
635 		delete node;
636 		break;
637 	}
638 }
639 
640 
TrashFDlgList(FDLG_LIST * i)641 static void TrashFDlgList(FDLG_LIST* i)
642 {
643 	while (i)
644 	{
645 		FDLG_LIST* const del = i;
646 		i = i->pNext;
647 		delete del;
648 	}
649 }
650 
651 
SetTopFileToLetter(UINT16 usLetter)652 static void SetTopFileToLetter(UINT16 usLetter)
653 {
654 	UINT32 x;
655 	FDLG_LIST *curr;
656 	FDLG_LIST *prev;
657 	UINT16 usNodeLetter;
658 
659 	// Skip to first filename
660 	x = 0;
661 	curr = prev = FileList;
662 	while( curr )
663 	{
664 		usNodeLetter = curr->filename[0]; //first letter of filename.
665 		if( usNodeLetter < 'a' )
666 			usNodeLetter += 32; //convert uppercase to lower case A=65, a=97
667 		if( usLetter <= usNodeLetter )
668 			break;
669 		prev = curr;
670 		curr = curr->pNext;
671 		x++;
672 	}
673 	if( FileList )
674 	{
675 		iCurrFileShown = x;
676 		iTopFileShown = x;
677 		if( iTopFileShown > iTotalFiles - 7 )
678 			iTopFileShown = iTotalFiles - 7;
679 		SetInputFieldString(0, prev->filename);
680 	}
681 }
682 
683 
HandleMainKeyEvents(InputAtom * pEvent)684 static void HandleMainKeyEvents(InputAtom* pEvent)
685 {
686 	INT32 iPrevFileShown = iCurrFileShown;
687 	//Replace Alt-x press with ESC.
688 	if( pEvent->usKeyState & ALT_DOWN && pEvent->usParam == 'x' )
689 		pEvent->usParam = SDLK_ESCAPE;
690 	switch( pEvent->usParam )
691 	{
692 		case SDLK_RETURN:
693 			if( gfNoFiles && iCurrentAction == ACTION_LOAD_MAP )
694 				break;
695 			iFDlgState = iCurrentAction == ACTION_SAVE_MAP ? DIALOG_SAVE : DIALOG_LOAD;
696 			break;
697 
698 		case SDLK_ESCAPE:
699 			iFDlgState = DIALOG_CANCEL;
700 			break;
701 
702 		case SDLK_PAGEUP:
703 			if( iTopFileShown > 7 )
704 			{
705 				iTopFileShown -= 7;
706 				iCurrFileShown -= 7;
707 			}
708 			else
709 			{
710 				iTopFileShown = 0;
711 				iCurrFileShown = 0;
712 			}
713 			break;
714 
715 		case SDLK_PAGEDOWN:
716 			iTopFileShown += 7;
717 			iCurrFileShown += 7;
718 			if( iTopFileShown > iTotalFiles-7 )
719 				iTopFileShown = iTotalFiles - 7;
720 			if( iCurrFileShown >= iTotalFiles )
721 				iCurrFileShown = iTotalFiles - 1;
722 			break;
723 
724 		case SDLK_UP:
725 			if( iCurrFileShown > 0 )
726 				iCurrFileShown--;
727 			if( iTopFileShown > iCurrFileShown )
728 				iTopFileShown = iCurrFileShown;
729 			break;
730 
731 		case SDLK_DOWN:
732 			iCurrFileShown++;
733 			if( iCurrFileShown >= iTotalFiles )
734 				iCurrFileShown = iTotalFiles - 1;
735 			else if( iTopFileShown < iCurrFileShown-7 )
736 				iTopFileShown++;
737 			break;
738 
739 		case SDLK_HOME:
740 			iTopFileShown = 0;
741 			iCurrFileShown = 0;
742 			break;
743 
744 		case SDLK_END:
745 			iTopFileShown = iTotalFiles-7;
746 			iCurrFileShown = iTotalFiles-1;
747 			break;
748 
749 		case SDLK_DELETE: iFDlgState = DIALOG_DELETE; break;
750 
751 		default:
752 			//This case handles jumping the file list to display the file with the letter pressed.
753 			if (pEvent->usParam >= SDLK_a && pEvent->usParam <= SDLK_z)
754 			{
755 				SetTopFileToLetter( (UINT16)pEvent->usParam );
756 			}
757 			break;
758 	}
759 	//Update the text field if the file value has changed.
760 	if( iCurrFileShown != iPrevFileShown )
761 	{
762 		INT32 x;
763 		FDLG_LIST *curr;
764 		x = 0;
765 		curr = FileList;
766 		while( curr && x != iCurrFileShown )
767 		{
768 			curr = curr->pNext;
769 			x++;
770 		}
771 		if( curr )
772 		{
773 			SetInputFieldString(0, curr->filename);
774 			gzFilename = ST::format("{}", curr->filename);
775 		}
776 	}
777 }
778 
779 
780 // Editor doesn't care about the z value. It uses its own methods.
SetGlobalSectorValues()781 static void SetGlobalSectorValues()
782 {
783 	{ const char* f = gzFilename.c_str();
784 
785 		INT16 y;
786 		if ('A' <= f[0] && f[0] <= 'P')
787 		{
788 			y = f[0] - 'A' + 1;
789 		}
790 		else if ('a' <= f[0] && f[0] <= 'p')
791 		{
792 			y = f[0] - 'a' + 1;
793 		}
794 		else goto invalid;
795 		++f;
796 
797 		INT16 x;
798 		if ('1' <= f[0] && f[0] <= '9' && (f[1] < '0' || '9' < f[1]))
799 		{ // 1 ... 9
800 			x = f[0] - '0';
801 			++f;
802 		}
803 		else if (f[0] == '1' && '0' <= f[1] && f[1] <= '6')
804 		{ // 10 ... 16
805 			x = (f[0] - '0') * 10 + f[1] - '0';
806 			f += 2;
807 		}
808 		else goto invalid;
809 
810 		INT8 z = 0;
811 		if (f[0] == '_' && f[1] == 'b' && '1' <= f[2] && f[2] <= '3')
812 		{
813 			z = f[2] - '0';
814 		}
815 
816 		gWorldSectorX  = x;
817 		gWorldSectorY  = y;
818 		gbWorldSectorZ = z;
819 		return;
820 	}
821 invalid:
822 	SetWorldSectorInvalid();
823 }
824 
825 
InitErrorCatchDialog(void)826 static void InitErrorCatchDialog(void)
827 {
828 	DoMessageBox(MSG_BOX_BASIC_STYLE, gzErrorCatchString, EDIT_SCREEN, MSG_BOX_FLAG_OK, NULL, NULL);
829 	gfErrorCatch = FALSE;
830 }
831 
832 
833 //Because loading and saving the map takes a few seconds, we want to post a message
834 //on the screen and then update it which requires passing the screen back to the main loop.
835 //When we come back for the next frame, we then actually save or load the map.  So this
836 //process takes two full screen cycles.
ProcessFileIO(void)837 static ScreenID ProcessFileIO(void)
838 {
839 	INT16 usStartX, usStartY;
840 	ST::string ubNewFilename;
841 	ST::string zOrigName;
842 	switch( gbCurrentFileIOStatus )
843 	{
844 		case INITIATE_MAP_SAVE:	//draw save message
845 			SaveFontSettings();
846 			SetFontAttributes(HUGEFONT, FONT_LTKHAKI, FONT_DKKHAKI);
847 			zOrigName = ST::format("Saving map:  {}", gzFilename);
848 			usStartX = (SCREEN_WIDTH - StringPixLength(zOrigName, HUGEFONT)) / 2;
849 			usStartY = 180 - GetFontHeight(HUGEFONT) / 2;
850 			MPrint(usStartX, usStartY, zOrigName);
851 
852 			InvalidateScreen( );
853 			gbCurrentFileIOStatus = SAVING_MAP;
854 			return LOADSAVE_SCREEN;
855 		case SAVING_MAP: //save map
856 			ubNewFilename = ST::format("{}", gzFilename);
857 			RaiseWorldLand();
858 			if( gfShowPits )
859 				RemoveAllPits();
860 			OptimizeSchedules();
861 			if ( !SaveWorld( ubNewFilename.c_str() ) )
862 			{
863 				if( gfErrorCatch )
864 				{
865 					InitErrorCatchDialog();
866 					return EDIT_SCREEN;
867 				}
868 				return ERROR_SCREEN;
869 			}
870 			if( gfShowPits )
871 				AddAllPits();
872 
873 			SetGlobalSectorValues();
874 
875 			if( gfGlobalSummaryExists )
876 				UpdateSectorSummary( gzFilename, gfUpdateSummaryInfo );
877 
878 			iCurrentAction = ACTION_NULL;
879 			gbCurrentFileIOStatus = IOSTATUS_NONE;
880 			gfRenderWorld = TRUE;
881 			gfRenderTaskbar = TRUE;
882 			fEnteringLoadSaveScreen = TRUE;
883 			RestoreFontSettings();
884 			if( gfErrorCatch )
885 			{
886 				InitErrorCatchDialog();
887 				return EDIT_SCREEN;
888 			}
889 			if( gMapInformation.ubMapVersion != gubMinorMapVersion )
890 				SLOGE("Map data has just been corrupted!!! What did you just do? KM : 0");
891 			return EDIT_SCREEN;
892 		case INITIATE_MAP_LOAD: //draw load message
893 			SaveFontSettings();
894 			gbCurrentFileIOStatus = LOADING_MAP;
895 			if( gfEditMode && iCurrentTaskbar == TASK_MERCS )
896 				IndicateSelectedMerc( SELECT_NO_MERC );
897 			SpecifyItemToEdit( NULL, -1 );
898 			return LOADSAVE_SCREEN;
899 		case LOADING_MAP: //load map
900 			DisableUndo();
901 			ubNewFilename = ST::format("{}", gzFilename);
902 
903 			RemoveMercsInSector( );
904 
905 			try
906 			{
907 				UINT32 const start = SDL_GetTicks();
908 				LoadWorld(ubNewFilename.c_str());
909 				fprintf(stderr, "---> %u\n", SDL_GetTicks() - start);
910 			}
911 			catch (...)
912 			{ //Want to override crash, so user can do something else.
913 				EnableUndo();
914 				SetPendingNewScreen( LOADSAVE_SCREEN );
915 				gbCurrentFileIOStatus = IOSTATUS_NONE;
916 				gfGlobalError = FALSE;
917 				gfLoadError = TRUE;
918 				//RemoveButton( iTempButton );
919 				CreateMessageBox( " Error loading file.  Try another filename?" );
920 				return LOADSAVE_SCREEN;
921 			}
922 			SetGlobalSectorValues();
923 
924 			RestoreFontSettings();
925 
926 			//Load successful, update necessary information.
927 
928 			AddSoldierInitListTeamToWorld(ENEMY_TEAM);
929 			AddSoldierInitListTeamToWorld(CREATURE_TEAM);
930 			AddSoldierInitListTeamToWorld(MILITIA_TEAM);
931 			AddSoldierInitListTeamToWorld(CIV_TEAM);
932 			iCurrentAction = ACTION_NULL;
933 			gbCurrentFileIOStatus = IOSTATUS_NONE;
934 			if( !gfCaves && !gfBasement )
935 			{
936 				gusLightLevel = 12;
937 				if( ubAmbientLightLevel != 4 )
938 				{
939 					ubAmbientLightLevel = 4;
940 					LightSetBaseLevel( ubAmbientLightLevel );
941 				}
942 			}
943 			else
944 				gusLightLevel = (UINT16)(EDITOR_LIGHT_MAX - ubAmbientLightLevel );
945 			gEditorLightColor = g_light_color;
946 			gfRenderWorld = TRUE;
947 			gfRenderTaskbar = TRUE;
948 			fEnteringLoadSaveScreen = TRUE;
949 			InitJA2SelectionWindow();
950 			ShowEntryPoints();
951 			EnableUndo();
952 			RemoveAllFromUndoList();
953 			SetEditorSmoothingMode( gMapInformation.ubEditorSmoothingType );
954 			if( gMapInformation.ubEditorSmoothingType == SMOOTHING_CAVES )
955 				AnalyseCaveMapForStructureInfo();
956 
957 			AddLockedDoorCursors();
958 			gubCurrRoomNumber = gubMaxRoomNumber;
959 			UpdateRoofsView();
960 			UpdateWallsView();
961 			ShowLightPositionHandles();
962 			SetMercTeamVisibility( ENEMY_TEAM, gfShowEnemies );
963 			SetMercTeamVisibility( CREATURE_TEAM, gfShowCreatures );
964 			SetMercTeamVisibility( MILITIA_TEAM, gfShowRebels );
965 			SetMercTeamVisibility( CIV_TEAM, gfShowCivilians );
966 			BuildItemPoolList();
967 			if( gfShowPits )
968 				AddAllPits();
969 
970 			if( iCurrentTaskbar == TASK_MAPINFO )
971 			{ //We have to temporarily remove the current textinput mode,
972 				//update the disabled text field values, then restore the current
973 				//text input fields.
974 				SaveAndRemoveCurrentTextInputMode();
975 				UpdateMapInfoFields();
976 				RestoreSavedTextInputMode();
977 			}
978 			return EDIT_SCREEN;
979 	}
980 	gbCurrentFileIOStatus = IOSTATUS_NONE;
981 	return LOADSAVE_SCREEN;
982 }
983 
984 
985 //LOADSCREEN
FDlgNamesCallback(GUI_BUTTON * butn,INT32 reason)986 static void FDlgNamesCallback(GUI_BUTTON* butn, INT32 reason)
987 {
988 	if( reason & (MSYS_CALLBACK_REASON_LBUTTON_UP) )
989 	{
990 		SelectFileDialogYPos(butn->RelativeY());
991 	}
992 }
993 
994 
FDlgOkCallback(GUI_BUTTON * butn,INT32 reason)995 static void FDlgOkCallback(GUI_BUTTON* butn, INT32 reason)
996 {
997 	if( reason & (MSYS_CALLBACK_REASON_LBUTTON_UP) )
998 	{
999 		iFDlgState = iCurrentAction == ACTION_SAVE_MAP ? DIALOG_SAVE : DIALOG_LOAD;
1000 	}
1001 }
1002 
1003 
FDlgCancelCallback(GUI_BUTTON * butn,INT32 reason)1004 static void FDlgCancelCallback(GUI_BUTTON* butn, INT32 reason)
1005 {
1006 	if( reason & (MSYS_CALLBACK_REASON_LBUTTON_UP) )
1007 	{
1008 		iFDlgState = DIALOG_CANCEL;
1009 	}
1010 }
1011 
1012 
FDlgUpCallback(GUI_BUTTON * butn,INT32 reason)1013 static void FDlgUpCallback(GUI_BUTTON* butn, INT32 reason)
1014 {
1015 	if( reason & (MSYS_CALLBACK_REASON_LBUTTON_UP) )
1016 	{
1017 		if(iTopFileShown > 0)
1018 			iTopFileShown--;
1019 	}
1020 }
1021 
1022 
FDlgDwnCallback(GUI_BUTTON * butn,INT32 reason)1023 static void FDlgDwnCallback(GUI_BUTTON* butn, INT32 reason)
1024 {
1025 	if( reason & (MSYS_CALLBACK_REASON_LBUTTON_UP) )
1026 	{
1027 		if( (iTopFileShown+7) < iTotalFiles )
1028 			iTopFileShown++;
1029 	}
1030 }
1031 
1032 
ExtractFilenameFromFields(void)1033 static BOOLEAN ExtractFilenameFromFields(void)
1034 {
1035 	gzFilename = GetStringFromField(0);
1036 	return ValidFilename();
1037 }
1038 
1039 
ValidFilename(void)1040 static BOOLEAN ValidFilename(void)
1041 {
1042 	if (!gzFilename.empty())
1043 	{
1044 		auto pos = gzFilename.find(".dat");
1045 		if (pos > 0 && gzFilename[pos + 4] == '\0' )
1046 			return TRUE;
1047 	}
1048 	return FALSE;
1049 }
1050 
ExternalLoadMap(const ST::string & szFilename)1051 BOOLEAN ExternalLoadMap(const ST::string& szFilename)
1052 {
1053 	if (szFilename.empty())
1054 		return FALSE;
1055 	gzFilename = szFilename;
1056 	if( !ValidFilename() )
1057 		return FALSE;
1058 	gbCurrentFileIOStatus = INITIATE_MAP_LOAD;
1059 	ProcessFileIO(); //always returns loadsave_screen and changes iostatus to loading_map.
1060 	ExecuteBaseDirtyRectQueue();
1061 	EndFrameBufferRender();
1062 	RefreshScreen();
1063 	if( ProcessFileIO() == EDIT_SCREEN )
1064 		return TRUE;
1065 	return FALSE;
1066 }
1067 
ExternalSaveMap(const ST::string & szFilename)1068 BOOLEAN ExternalSaveMap(const ST::string& szFilename)
1069 {
1070 	if (szFilename.empty())
1071 		return FALSE;
1072 	gzFilename = szFilename;
1073 	if( !ValidFilename() )
1074 		return FALSE;
1075 	gbCurrentFileIOStatus = INITIATE_MAP_SAVE;
1076 	if( ProcessFileIO() == ERROR_SCREEN )
1077 		return FALSE;
1078 	ExecuteBaseDirtyRectQueue();
1079 	EndFrameBufferRender();
1080 	RefreshScreen();
1081 	if( ProcessFileIO() == EDIT_SCREEN )
1082 		return TRUE;
1083 	return FALSE;
1084 }
1085