1 /*
2  * Copyright (C) 2012-2013 Me and My Shadow
3  *
4  * This file is part of Me and My Shadow.
5  *
6  * Me and My Shadow is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Me and My Shadow is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Me and My Shadow.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "LevelEditSelect.h"
21 #include "GameState.h"
22 #include "Functions.h"
23 #include "FileManager.h"
24 #include "Globals.h"
25 #include "GUIObject.h"
26 #include "GUIListBox.h"
27 #include "GUIScrollBar.h"
28 #include "GUISpinBox.h"
29 #include "InputManager.h"
30 #include "StatisticsManager.h"
31 #include "Game.h"
32 #include "GUIOverlay.h"
33 #include <algorithm>
34 #include <string>
35 #include <iostream>
36 
37 #include "libs/tinyformat/tinyformat.h"
38 
39 using namespace std;
40 
LevelEditSelect(ImageManager & imageManager,SDL_Renderer & renderer)41 LevelEditSelect::LevelEditSelect(ImageManager& imageManager, SDL_Renderer& renderer):LevelSelect(imageManager,renderer,_("Map Editor"),LevelPackManager::CUSTOM_PACKS){
42 	//Create the gui.
43     createGUI(imageManager,renderer, true);
44 
45 	//Set the levelEditGUIObjectRoot.
46 	levelEditGUIObjectRoot=GUIObjectRoot;
47 
48 	//show level list
49 	changePack();
50     refresh(imageManager, renderer);
51 }
52 
~LevelEditSelect()53 LevelEditSelect::~LevelEditSelect(){
54 	selectedNumber=NULL;
55 }
56 
createGUI(ImageManager & imageManager,SDL_Renderer & renderer,bool initial)57 void LevelEditSelect::createGUI(ImageManager& imageManager,SDL_Renderer &renderer, bool initial){
58 	if(initial){
59 		//The levelpack name text field.
60         levelpackName=new GUITextBox(imageManager,renderer,280,104,240,32);
61 		levelpackName->eventCallback=this;
62 		levelpackName->visible=false;
63 		GUIObjectRoot->addChild(levelpackName);
64 
65 		//Create the six buttons at the bottom of the screen.
66 		newPack = new GUIButton(imageManager, renderer, 0, 0, -1, 32, _("New Levelpack"));
67 		newPack->name = "cmdNewLvlpack";
68 		newPack->eventCallback = this;
69 		GUIObjectRoot->addChild(newPack);
70 
71 		propertiesPack = new GUIButton(imageManager, renderer, 0, 0, -1, 32, _("Pack Properties"), 0, true, true, GUIGravityCenter);
72 		propertiesPack->name = "cmdLvlpackProp";
73 		propertiesPack->eventCallback = this;
74 		GUIObjectRoot->addChild(propertiesPack);
75 
76 		removePack = new GUIButton(imageManager, renderer, 0, 0, -1, 32, _("Remove Pack"), 0, true, true, GUIGravityRight);
77 		removePack->name = "cmdRmLvlpack";
78 		removePack->eventCallback = this;
79 		GUIObjectRoot->addChild(removePack);
80 
81 		move = new GUIButton(imageManager, renderer, 0, 0, -1, 32, _("Move Map"));
82 		move->name = "cmdMoveMap";
83 		move->eventCallback = this;
84 		//NOTE: Set enabled equal to the inverse of initial.
85 		//When resizing the window initial will be false and therefor the move button can stay enabled.
86 		move->enabled = false;
87 		GUIObjectRoot->addChild(move);
88 
89 		remove = new GUIButton(imageManager, renderer, 0, 0, -1, 32, _("Remove Map"), 0, false, true, GUIGravityCenter);
90 		remove->name = "cmdRmMap";
91 		remove->eventCallback = this;
92 		GUIObjectRoot->addChild(remove);
93 
94 		edit = new GUIButton(imageManager, renderer, 0, 0, -1, 32, _("Edit Map"), 0, false, true, GUIGravityRight);
95 		edit->name = "cmdEdit";
96 		edit->eventCallback = this;
97 		GUIObjectRoot->addChild(edit);
98 	}
99 
100 	//Move buttons to default position
101 	const int x1 = int(SCREEN_WIDTH*0.02), x2 = int(SCREEN_WIDTH*0.5), x3 = int(SCREEN_WIDTH*0.98);
102 	const int y1 = SCREEN_HEIGHT - 120, y2 = SCREEN_HEIGHT - 60;
103 	newPack->left = x1; newPack->top = y1; newPack->gravity = GUIGravityLeft;
104 	propertiesPack->left = x2; propertiesPack->top = y1; propertiesPack->gravity = GUIGravityCenter;
105 	removePack->left = x3; removePack->top = y1; removePack->gravity = GUIGravityRight;
106 	move->left = x1; move->top = y2; move->gravity = GUIGravityLeft;
107 	remove->left = x2; remove->top = y2; remove->gravity = GUIGravityCenter;
108 	edit->left = x3; edit->top = y2; edit->gravity = GUIGravityRight;
109 
110 	isVertical = false;
111 
112 	//Reset the font size
113 	newPack->smallFont = false; newPack->width = -1;
114 	propertiesPack->smallFont = false; propertiesPack->width = -1;
115 	removePack->smallFont = false; removePack->width = -1;
116 	move->smallFont = false; move->width = -1;
117 	remove->smallFont = false; remove->width = -1;
118 	edit->smallFont = false; edit->width = -1;
119 
120 	//Now update widgets and then check if they overlap
121 	GUIObjectRoot->render(renderer, 0, 0, false);
122 	if (propertiesPack->left - propertiesPack->gravityX < newPack->left + newPack->width ||
123 		propertiesPack->left - propertiesPack->gravityX + propertiesPack->width > removePack->left - removePack->gravityX)
124 	{
125 		newPack->smallFont = true; newPack->width = -1;
126 		propertiesPack->smallFont = true; propertiesPack->width = -1;
127 		removePack->smallFont = true; removePack->width = -1;
128 		move->smallFont = true; move->width = -1;
129 		remove->smallFont = true; remove->width = -1;
130 		edit->smallFont = true; edit->width = -1;
131 	}
132 
133 	// NOTE: the following code is necessary (e.g. for Germany)
134 
135 	//Check again
136 	GUIObjectRoot->render(renderer, 0, 0, false);
137 	if (propertiesPack->left - propertiesPack->gravityX < newPack->left + newPack->width ||
138 		propertiesPack->left - propertiesPack->gravityX + propertiesPack->width > removePack->left - removePack->gravityX)
139 	{
140 		newPack->left = SCREEN_WIDTH*0.02;
141 		newPack->top = SCREEN_HEIGHT - 140;
142 		newPack->smallFont = false;
143 		newPack->width = -1;
144 		newPack->gravity = GUIGravityLeft;
145 
146 		propertiesPack->left = SCREEN_WIDTH*0.02;
147 		propertiesPack->top = SCREEN_HEIGHT - 100;
148 		propertiesPack->smallFont = false;
149 		propertiesPack->width = -1;
150 		propertiesPack->gravity = GUIGravityLeft;
151 
152 		removePack->left = SCREEN_WIDTH*0.02;
153 		removePack->top = SCREEN_HEIGHT - 60;
154 		removePack->smallFont = false;
155 		removePack->width = -1;
156 		removePack->gravity = GUIGravityLeft;
157 
158 		move->left = SCREEN_WIDTH*0.98;
159 		move->top = SCREEN_HEIGHT - 140;
160 		move->smallFont = false;
161 		move->width = -1;
162 		move->gravity = GUIGravityRight;
163 
164 		remove->left = SCREEN_WIDTH*0.98;
165 		remove->top = SCREEN_HEIGHT - 100;
166 		remove->smallFont = false;
167 		remove->width = -1;
168 		remove->gravity = GUIGravityRight;
169 
170 		edit->left = SCREEN_WIDTH*0.98;
171 		edit->top = SCREEN_HEIGHT - 60;
172 		edit->smallFont = false;
173 		edit->width = -1;
174 		edit->gravity = GUIGravityRight;
175 
176 		isVertical = true;
177 	}
178 }
179 
changePack()180 void LevelEditSelect::changePack(){
181 	packPath = levelpacks->item[levelpacks->value].first;
182 	packName = levelpacks->item[levelpacks->value].second;
183 	if(packPath==CUSTOM_LEVELS_PATH){
184 		//Disable some levelpack buttons.
185 		propertiesPack->enabled=false;
186 		removePack->enabled=false;
187 	}else{
188 		//Enable some levelpack buttons.
189 		propertiesPack->enabled=true;
190 		removePack->enabled=true;
191 	}
192 
193 	//Set last levelpack.
194 	getSettings()->setValue("lastlevelpack",levelpacks->getName());
195 
196 	//Now let levels point to the right pack.
197 	levels=getLevelPackManager()->getLevelPack(levelpacks->getName());
198 
199 	//invalidate the tooltip
200 	toolTip.number = -1;
201 }
202 
packProperties(ImageManager & imageManager,SDL_Renderer & renderer,bool newPack)203 void LevelEditSelect::packProperties(ImageManager& imageManager,SDL_Renderer& renderer, bool newPack){
204 	//Open a message popup.
205     GUIObject* root=new GUIFrame(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-390)/2,600,390,_("Properties"));
206 	GUIObject* obj;
207 
208     obj=new GUILabel(imageManager,renderer,40,50,240,36,_("Name:"));
209 	root->addChild(obj);
210 
211     obj=new GUITextBox(imageManager,renderer,60,80,480,36,packName.c_str());
212 	if(newPack)
213 		obj->caption="";
214 	obj->name="LvlpackName";
215 	root->addChild(obj);
216 
217     obj=new GUILabel(imageManager,renderer,40,120,240,36,_("Description:"));
218 	root->addChild(obj);
219 
220     obj=new GUITextBox(imageManager,renderer,60,150,480,36,levels->levelpackDescription.c_str());
221 	if(newPack)
222 		obj->caption="";
223 	obj->name="LvlpackDescription";
224 	root->addChild(obj);
225 
226     obj=new GUILabel(imageManager,renderer,40,190,240,36,_("Congratulation text:"));
227 	root->addChild(obj);
228 
229     obj=new GUITextBox(imageManager,renderer,60,220,480,36,levels->congratulationText.c_str());
230 	if(newPack)
231 		obj->caption="";
232 	obj->name="LvlpackCongratulation";
233 	root->addChild(obj);
234 
235 	obj = new GUILabel(imageManager, renderer, 40, 260, 240, 36, _("Music list:"));
236 	root->addChild(obj);
237 
238 	obj = new GUITextBox(imageManager, renderer, 60, 290, 480, 36, levels->levelpackMusicList.c_str());
239 	if (newPack)
240 		obj->caption = "";
241 	obj->name = "LvlpackMusic";
242 	root->addChild(obj);
243 
244     obj=new GUIButton(imageManager,renderer,root->width*0.3,390-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
245 	obj->name="cfgOK";
246 	obj->eventCallback=this;
247 	root->addChild(obj);
248 	GUIButton *cancelButton = new GUIButton(imageManager, renderer, root->width*0.7, 390 - 44, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
249 	cancelButton->name = "cfgCancel";
250 	cancelButton->eventCallback = this;
251 	root->addChild(cancelButton);
252 
253 	//Create the gui overlay.
254 	//NOTE: We don't need to store a pointer since it will auto cleanup itself.
255 	new AddonOverlay(renderer, root, cancelButton, NULL, UpDownFocus | TabFocus | ReturnControls | LeftRightControls);
256 
257 	if(newPack){
258 		packPath.clear();
259 		packName.clear();
260 	}
261 }
262 
addLevel(ImageManager & imageManager,SDL_Renderer & renderer)263 void LevelEditSelect::addLevel(ImageManager& imageManager,SDL_Renderer& renderer){
264 	//Open a message popup.
265     GUIObject* root=new GUIFrame(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-200)/2,600,200,_("Add level"));
266 	GUIObject* obj;
267 
268     obj=new GUILabel(imageManager,renderer,40,80,240,36,_("File name:"));
269 	root->addChild(obj);
270 
271 	char s[64];
272     SDL_snprintf(s,64,"map%02d.map",levels->getLevelCount()+1);
273     obj=new GUITextBox(imageManager,renderer,300,80,240,36,s);
274 	obj->name="LvlFile";
275 	root->addChild(obj);
276 
277     obj=new GUIButton(imageManager,renderer,root->width*0.3,200-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
278 	obj->name="cfgAddOK";
279 	obj->eventCallback=this;
280 	root->addChild(obj);
281 	GUIButton *cancelButton = new GUIButton(imageManager, renderer, root->width*0.7, 200 - 44, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
282 	cancelButton->name = "cfgAddCancel";
283 	cancelButton->eventCallback = this;
284 	root->addChild(cancelButton);
285 
286     //Dim the screen using the tempSurface.
287 	//NOTE: We don't need to store a pointer since it will auto cleanup itself.
288 	new AddonOverlay(renderer, root, cancelButton, NULL, UpDownFocus | TabFocus | ReturnControls | LeftRightControls);
289 }
290 
moveLevel(ImageManager & imageManager,SDL_Renderer & renderer)291 void LevelEditSelect::moveLevel(ImageManager& imageManager,SDL_Renderer& renderer){
292 	//Open a message popup.
293     GUIObject* root=new GUIFrame(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-200)/2,600,200,_("Move level"));
294 	GUIObject* obj;
295 
296     obj=new GUILabel(imageManager,renderer,40,60,240,36,_("Level: "));
297 	root->addChild(obj);
298 
299 	GUISpinBox *spinBox = new GUISpinBox(imageManager, renderer, 300, 60, 240, 36);
300 	spinBox->caption = tfm::format("%d", selectedNumber->getNumber() + 1);
301 	spinBox->format = "%1.0f";
302 	spinBox->limitMin = 1.0f;
303 	spinBox->limitMax = float(levels->getLevelCount());
304 	spinBox->name = "MoveLevel";
305 	root->addChild(spinBox);
306 
307     obj=new GUISingleLineListBox(imageManager,renderer,root->width*0.5,110,240,36,true,true,GUIGravityCenter);
308 	obj->name="lstPlacement";
309 	vector<string> v;
310 	v.push_back(_("Before"));
311 	v.push_back(_("After"));
312 	v.push_back(_("Swap"));
313 	(dynamic_cast<GUISingleLineListBox*>(obj))->addItems(v);
314 	obj->value=0;
315 	root->addChild(obj);
316 
317     obj=new GUIButton(imageManager,renderer,root->width*0.3,200-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
318 	obj->name="cfgMoveOK";
319 	obj->eventCallback=this;
320 	root->addChild(obj);
321 	GUIButton *cancelButton = new GUIButton(imageManager, renderer, root->width*0.7, 200 - 44, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
322 	cancelButton->name = "cfgMoveCancel";
323 	cancelButton->eventCallback = this;
324 	root->addChild(cancelButton);
325 
326 	//Create the gui overlay.
327 	//NOTE: We don't need to store a pointer since it will auto cleanup itself.
328 	new AddonOverlay(renderer, root, cancelButton, NULL, TabFocus | ReturnControls | LeftRightControls);
329 }
330 
refresh(ImageManager & imageManager,SDL_Renderer & renderer,bool change)331 void LevelEditSelect::refresh(ImageManager& imageManager, SDL_Renderer& renderer, bool change){
332 	int m=levels->getLevelCount();
333 
334 	if(change){
335 		numbers.clear();
336 
337 		//clear the selected level
338 		if(selectedNumber!=NULL){
339 			selectedNumber=NULL;
340 		}
341 
342 		//Disable the level specific buttons.
343 		move->enabled=false;
344 		remove->enabled=false;
345 		edit->enabled=false;
346 
347 		for(int n=0;n<=m;n++){
348             numbers.emplace_back(imageManager, renderer);
349 		}
350 	}
351 
352 	for(int n=0;n<m;n++){
353 		SDL_Rect box={(n%LEVELS_PER_ROW)*64+80,(n/LEVELS_PER_ROW)*64+184,0,0};
354         numbers[n].init(renderer,n,box);
355 	}
356 	SDL_Rect box={(m%LEVELS_PER_ROW)*64+80,(m/LEVELS_PER_ROW)*64+184,0,0};
357     numbers[m].init(renderer,"+",box,m);
358 
359 	m++; //including the "+" button
360 	if(m>LEVELS_DISPLAYED_IN_SCREEN){
361 		levelScrollBar->maxValue=(m-LEVELS_DISPLAYED_IN_SCREEN+LEVELS_PER_ROW-1)/LEVELS_PER_ROW;
362 		levelScrollBar->visible=true;
363 	}else{
364 		levelScrollBar->maxValue=0;
365 		levelScrollBar->visible=false;
366 	}
367 	if (levels->levelpackPath == LEVELS_PATH || levels->levelpackPath == CUSTOM_LEVELS_PATH)
368 		levelpackDescription->caption = _("Individual levels which are not contained in any level packs");
369 	else if (!levels->levelpackDescription.empty())
370 		levelpackDescription->caption = _CC(levels->getDictionaryManager(), levels->levelpackDescription);
371 	else
372 		levelpackDescription->caption = "";
373 
374 	//invalidate the tooltip
375 	toolTip.number = -1;
376 }
377 
selectNumber(ImageManager & imageManager,SDL_Renderer & renderer,unsigned int number,bool selected)378 void LevelEditSelect::selectNumber(ImageManager& imageManager, SDL_Renderer& renderer, unsigned int number, bool selected){
379 	if (selected) {
380 		if (number >= 0 && number < levels->getLevelCount()) {
381 			levels->setCurrentLevel(number);
382 			setNextState(STATE_LEVEL_EDITOR);
383 		} else {
384 			addLevel(imageManager, renderer);
385 		}
386 	}else{
387 		move->enabled = false;
388 		remove->enabled = false;
389 		edit->enabled = false;
390 		selectedNumber = NULL;
391 		if (number == numbers.size() - 1){
392 			if (isKeyboardOnly) {
393 				selectedNumber = &numbers[number];
394 			} else {
395 				addLevel(imageManager, renderer);
396 			}
397 		} else if (number >= 0 && number < levels->getLevelCount()) {
398 			selectedNumber=&numbers[number];
399 
400 			//Enable the level specific buttons.
401 			//NOTE: We check if 'remove levelpack' is enabled, if not then it's the Levels levelpack.
402 			if(removePack->enabled)
403 				move->enabled=true;
404 			remove->enabled=true;
405 			edit->enabled=true;
406 		}
407 	}
408 }
409 
handleEvents(ImageManager & imageManager,SDL_Renderer & renderer)410 void LevelEditSelect::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
411 	//Call handleEvents() of base class.
412 	LevelSelect::handleEvents(imageManager, renderer);
413 
414 	if (section == 3) {
415 		//Check focus movement
416 		if (inputMgr.isKeyDownEvent(INPUTMGR_RIGHT)){
417 			isKeyboardOnly = true;
418 			section2 += isVertical ? 3 : 1;
419 		} else if (inputMgr.isKeyDownEvent(INPUTMGR_LEFT)){
420 			isKeyboardOnly = true;
421 			section2 -= isVertical ? 3 : 1;
422 		} else if (inputMgr.isKeyDownEvent(INPUTMGR_UP)){
423 			isKeyboardOnly = true;
424 			section2 -= isVertical ? 1 : 3;
425 		} else if (inputMgr.isKeyDownEvent(INPUTMGR_DOWN)){
426 			isKeyboardOnly = true;
427 			section2 += isVertical ? 1 : 3;
428 		}
429 		if (section2 > 6) section2 -= 6;
430 		else if (section2 < 1) section2 += 6;
431 
432 		//Check if enter is pressed
433 		if (isKeyboardOnly && inputMgr.isKeyDownEvent(INPUTMGR_SELECT) && section2 >= 1 && section2 <= 6) {
434 			GUIButton *buttons[6] = {
435 				newPack, propertiesPack, removePack, move, remove, edit
436 			};
437 			GUIEventCallback_OnEvent(imageManager, renderer, buttons[section2 - 1]->name, buttons[section2 - 1], GUIEventClick);
438 		}
439 	}
440 }
441 
render(ImageManager & imageManager,SDL_Renderer & renderer)442 void LevelEditSelect::render(ImageManager& imageManager,SDL_Renderer &renderer){
443 	//Let the levelselect render.
444     LevelSelect::render(imageManager,renderer);
445 
446 	//Draw highlight in keyboard only mode.
447 	if (isKeyboardOnly) {
448 		GUIButton *buttons[6] = {
449 			newPack, propertiesPack, removePack, move, remove, edit
450 		};
451 		for (int i = 0; i < 6; i++) {
452 			buttons[i]->state = (section == 3 && section2 - 1 == i) ? 1 : 0;
453 		}
454 	}
455 }
456 
resize(ImageManager & imageManager,SDL_Renderer & renderer)457 void LevelEditSelect::resize(ImageManager& imageManager, SDL_Renderer &renderer){
458 	//Let the levelselect resize.
459     LevelSelect::resize(imageManager, renderer);
460 
461 	//Create the GUI.
462     createGUI(imageManager,renderer, false);
463 
464 	//NOTE: This is a workaround for buttons failing when resizing.
465 	if(packPath==CUSTOM_LEVELS_PATH){
466 		removePack->enabled=false;
467 		propertiesPack->enabled=false;
468 	}
469 	if(selectedNumber)
470         selectNumber(imageManager, renderer, selectedNumber->getNumber(),false);
471 
472 }
473 
renderTooltip(SDL_Renderer & renderer,unsigned int number,int dy)474 void LevelEditSelect::renderTooltip(SDL_Renderer& renderer,unsigned int number,int dy){
475 
476     if (!toolTip.name || toolTip.number != number) {
477 		SDL_Color fg = objThemes.getTextColor(true);
478         toolTip.number = number;
479 
480 		if (number < (unsigned int)levels->getLevelCount()){
481 			//Render the name of the level.
482 			toolTip.name = textureFromText(renderer, *fontText, _CC(levels->getDictionaryManager(), levels->getLevelName(number)), fg);
483 		} else {
484 			//Add level button
485 			toolTip.name = textureFromText(renderer, *fontText, _("Add level"), fg);
486 		}
487     }
488 
489 	//Check if name isn't null.
490     if(!toolTip.name)
491 		return;
492 
493 	//Now draw a square the size of the three texts combined.
494 	SDL_Rect r=numbers[number].box;
495 	r.y-=dy*64;
496     const SDL_Rect nameSize = rectFromTexture(*toolTip.name);
497     r.w=nameSize.w;
498     r.h=nameSize.h;
499 
500 	//Make sure the tooltip doesn't go outside the window.
501 	if(r.y>SCREEN_HEIGHT-200){
502         r.y-=nameSize.h+4;
503 	}else{
504 		r.y+=numbers[number].box.h+2;
505 	}
506 	if(r.x+r.w>SCREEN_WIDTH-50)
507 		r.x=SCREEN_WIDTH-50-r.w;
508 
509 	//Draw a rectange
510 	Uint32 color=0xFFFFFFFF;
511     drawGUIBox(r.x-5,r.y-5,r.w+10,r.h+10,renderer,color);
512 
513 	//Calc the position to draw.
514 	SDL_Rect r2=r;
515 
516 	//Now we render the name if the surface isn't null.
517     if(toolTip.name){
518 		//Draw the name.
519         applyTexture(r2.x, r2.y, toolTip.name, renderer);
520 	}
521 }
522 
523 //Escape invalid characters in a file name (mainly for Windows).
escapeFileName(const std::string & fileName)524 static std::string escapeFileName(const std::string& fileName) {
525 	std::string ret;
526 
527 	for (int i = 0, m = fileName.size(); i < m; i++) {
528 		bool escape = false;
529 		char c = fileName[i];
530 
531 		switch (c) {
532 		case '\"': case '*': case '/': case ':': case '<':
533 		case '>': case '?': case '\\': case '|': case '%':
534 			escape = true;
535 			break;
536 		}
537 		if (c <= 0x1F || c >= 0x7F) escape = true;
538 		if (i == 0 || i == m - 1) {
539 			switch (c) {
540 			case ' ': case '.':
541 				escape = true;
542 				break;
543 			}
544 		}
545 
546 		if (escape) {
547 			ret += "%" + tfm::format("%02X", (int)(unsigned char)c);
548 		} else {
549 			ret.push_back(c);
550 		}
551 	}
552 
553 	return ret;
554 }
555 
GUIEventCallback_OnEvent(ImageManager & imageManager,SDL_Renderer & renderer,std::string name,GUIObject * obj,int eventType)556 void LevelEditSelect::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
557 	//NOTE: We check for the levelpack change to enable/disable some levelpack buttons.
558 	if(name=="cmdLvlPack"){
559 		//We call changepack and return to prevent the LevelSelect to undo what we did.
560 		changePack();
561         refresh(imageManager, renderer);
562 		return;
563 	}
564 
565 	//Let the level select handle his GUI events.
566     LevelSelect::GUIEventCallback_OnEvent(imageManager,renderer,name,obj,eventType);
567 
568 	//Check for the edit button.
569 	if(name=="cmdNewLvlpack"){
570 		//Create a new pack.
571         packProperties(imageManager,renderer, true);
572 	}else if(name=="cmdLvlpackProp"){
573 		//Show the pack properties.
574         packProperties(imageManager,renderer, false);
575 	}else if(name=="cmdRmLvlpack"){
576 		//Show an "are you sure" message.
577         if(msgBox(imageManager,renderer,tfm::format(_("Are you sure remove the level pack '%s'?"),packName),MsgBoxYesNo,_("Remove prompt"))==MsgBoxYes){
578 			//Remove the directory.
579 			if(!removeDirectory(levels->levelpackPath.c_str())){
580 				cerr<<"ERROR: Unable to remove levelpack directory "<<levels->levelpackPath<<endl;
581 			}
582 
583 			//Remove it from the vector (levelpack list).
584 			for (auto it = levelpacks->item.begin(); it != levelpacks->item.end(); ++it){
585 				if (it->first == levels->levelpackPath) {
586 					levelpacks->item.erase(it);
587 					break;
588 				}
589 			}
590 
591 			//Remove it from the levelpackManager.
592 			getLevelPackManager()->removeLevelPack(levels->levelpackPath, true);
593 			levels = NULL;
594 
595 			//And call changePack.
596 			levelpacks->value=levelpacks->item.size()-1;
597 			changePack();
598             refresh(imageManager, renderer);
599 		}
600 	}else if(name=="cmdMoveMap"){
601 		if(selectedNumber!=NULL){
602             moveLevel(imageManager,renderer);
603 		}
604 	}else if(name=="cmdRmMap"){
605 		if(selectedNumber!=NULL){
606 			//Show an "are you sure" message.
607 			if (msgBox(imageManager, renderer, tfm::format(_("Are you sure remove the map '%s'?"), levels->getLevel(selectedNumber->getNumber())->name), MsgBoxYesNo, _("Remove prompt")) != MsgBoxYes) {
608 				return;
609 			}
610 			if(packPath!=CUSTOM_LEVELS_PATH){
611 				if(!removeFile((levels->levelpackPath+"/"+levels->getLevel(selectedNumber->getNumber())->file).c_str())){
612 					cerr<<"ERROR: Unable to remove level "<<(levels->levelpackPath+"/"+levels->getLevel(selectedNumber->getNumber())->file).c_str()<<endl;
613 				}
614 				levels->removeLevel(selectedNumber->getNumber());
615 				levels->saveLevels(levels->levelpackPath+"/levels.lst");
616 			}else{
617 				//This is the levels levelpack so we just remove the file.
618 				if(!removeFile(levels->getLevel(selectedNumber->getNumber())->file.c_str())){
619 					cerr<<"ERROR: Unable to remove level "<<levels->getLevel(selectedNumber->getNumber())->file<<endl;
620 				}
621 				levels->removeLevel(selectedNumber->getNumber());
622 			}
623 
624 			//And refresh the selection screen.
625             refresh(imageManager, renderer);
626 		}
627 	}else if(name=="cmdEdit"){
628 		if(selectedNumber!=NULL){
629 			levels->setCurrentLevel(selectedNumber->getNumber());
630 			setNextState(STATE_LEVEL_EDITOR);
631 		}
632 	}
633 
634 	//Check for levelpack properties events.
635 	if(name=="cfgOK"){
636 		GUIObject *lvlpackName = GUIObjectRoot->getChild("LvlpackName");
637 		GUIObject *lvlpackDescription = GUIObjectRoot->getChild("LvlpackDescription");
638 		GUIObject *lvlpackCongratulation = GUIObjectRoot->getChild("LvlpackCongratulation");
639 		GUIObject *lvlpackMusic = GUIObjectRoot->getChild("LvlpackMusic");
640 
641 		assert(lvlpackName && lvlpackDescription && lvlpackCongratulation && lvlpackMusic);
642 
643 		if (lvlpackName->caption.empty()) {
644 			msgBox(imageManager, renderer, _("Levelpack name cannot be empty."), MsgBoxOKOnly, _("Error"));
645 		} else {
646 			//Check if the name changed.
647 			if (packName != lvlpackName->caption) {
648 				std::string newPackPathMinusSlash = getUserPath(USER_DATA) + "custom/levelpacks/" + escapeFileName(lvlpackName->caption);
649 
650 				//Delete the old one.
651 				if (!packName.empty()){
652 					std::string oldPackPathMinusSlash = levels->levelpackPath;
653 					if (!oldPackPathMinusSlash.empty()) {
654 						if (oldPackPathMinusSlash[oldPackPathMinusSlash.size() - 1] == '/'
655 							|| oldPackPathMinusSlash[oldPackPathMinusSlash.size() - 1] == '\\')
656 						{
657 							oldPackPathMinusSlash.pop_back();
658 						}
659 					}
660 
661 					if (!renameDirectory(oldPackPathMinusSlash.c_str(), newPackPathMinusSlash.c_str())) {
662 						cerr << "ERROR: Unable to move levelpack directory '" << oldPackPathMinusSlash << "' to '"
663 							<< newPackPathMinusSlash << "'! The levelpack directory will be kept unchanged." << endl;
664 
665 						//If we failed to rename the directory, we just keep the old directory name.
666 						newPackPathMinusSlash = oldPackPathMinusSlash;
667 					}
668 
669 					//Remove the old one from the levelpack manager.
670 					getLevelPackManager()->removeLevelPack(levels->levelpackPath, false);
671 
672 					//And the levelpack list.
673 					for (auto it = levelpacks->item.begin(); it != levelpacks->item.end(); ++it){
674 						if (it->first == levels->levelpackPath) {
675 							levelpacks->item.erase(it);
676 							break;
677 						}
678 					}
679 				} else {
680 					//It's a new levelpack. First we try to create the dirs and the levels.lst.
681 					if (dirExists(newPackPathMinusSlash.c_str())) {
682 						cerr << "ERROR: The levelpack directory " << newPackPathMinusSlash << " already exists!" << endl;
683 						msgBox(imageManager, renderer, tfm::format(_("The levelpack directory '%s' already exists!"), newPackPathMinusSlash), MsgBoxOKOnly, _("Error"));
684 						return;
685 					}
686 					if (!createDirectory(newPackPathMinusSlash.c_str())) {
687 						cerr << "ERROR: Unable to create levelpack directory " << newPackPathMinusSlash << endl;
688 						msgBox(imageManager, renderer, tfm::format(_("Unable to create levelpack directory '%s'!"), newPackPathMinusSlash), MsgBoxOKOnly, _("Error"));
689 						return;
690 					}
691 					if (fileExists((newPackPathMinusSlash + "/levels.lst").c_str())) {
692 						cerr << "ERROR: The levelpack file " << (newPackPathMinusSlash + "/levels.lst") << " already exists!" << endl;
693 						msgBox(imageManager, renderer, tfm::format(_("The levelpack file '%s' already exists!"), newPackPathMinusSlash + "/levels.lst"), MsgBoxOKOnly, _("Error"));
694 						return;
695 					}
696 					if (!createFile((newPackPathMinusSlash + "/levels.lst").c_str())) {
697 						cerr << "ERROR: Unable to create levelpack file " << (newPackPathMinusSlash + "/levels.lst") << endl;
698 						msgBox(imageManager, renderer, tfm::format(_("Unable to create levelpack file '%s'!"), newPackPathMinusSlash + "/levels.lst"), MsgBoxOKOnly, _("Error"));
699 						return;
700 					}
701 
702 					//If it's successful we create a new levelpack.
703 					levels = new LevelPack;
704 				}
705 
706 				//And set the new name.
707 				packName = levels->levelpackName = lvlpackName->caption;
708 				packPath = levels->levelpackPath = newPackPathMinusSlash + "/";
709 
710 				//Also add the levelpack location
711 				getLevelPackManager()->addLevelPack(levels);
712 				levelpacks->addItem(packPath, packName);
713 				levelpacks->value = levelpacks->item.size() - 1;
714 
715 				//And call changePack.
716 				changePack();
717 			}
718 
719 			levels->levelpackDescription = lvlpackDescription->caption;
720 			levels->congratulationText = lvlpackCongratulation->caption;
721 			levels->levelpackMusicList = lvlpackMusic->caption;
722 
723 			//Refresh the leveleditselect to show the correct information.
724 			refresh(imageManager, renderer);
725 
726 			//Save the configuration.
727 			levels->saveLevels(levels->levelpackPath + "levels.lst");
728 			getSettings()->setValue("lastlevelpack", levels->levelpackPath);
729 
730 			//Clear the gui.
731 			if (GUIObjectRoot) {
732 				delete GUIObjectRoot;
733 				GUIObjectRoot = NULL;
734 			}
735 		}
736 	}else if(name=="cfgCancel"){
737 		//Check if packName is empty, if so it was a new levelpack and we need to revert to an existing one.
738 		if(packName.empty()){
739 			packPath = levelpacks->item[levelpacks->value].first;
740 			packName = levelpacks->item[levelpacks->value].second;
741 			changePack();
742 		}
743 
744 		//Clear the gui.
745 		if(GUIObjectRoot){
746 			delete GUIObjectRoot;
747 			GUIObjectRoot=NULL;
748 		}
749 	}
750 
751 	//Check for add level events.
752 	if(name=="cfgAddOK"){
753 		//Check if the file name isn't null.
754 		//Now loop throught the children of the GUIObjectRoot in search of the fields.
755 		for(unsigned int i=0;i<GUIObjectRoot->childControls.size();i++){
756 			if(GUIObjectRoot->childControls[i]->name=="LvlFile"){
757 				if(GUIObjectRoot->childControls[i]->caption.empty()){
758                     msgBox(imageManager,renderer,_("No file name given for the new level."),MsgBoxOKOnly,_("Missing file name"));
759 					return;
760 				}else{
761 					string tmp_caption = GUIObjectRoot->childControls[i]->caption;
762 
763 					//Replace all spaces with a underline.
764 					size_t j;
765 					for(;(j=tmp_caption.find(" "))!=string::npos;){
766 						tmp_caption.replace(j,1,"_");
767 					}
768 
769 					//If there isn't ".map" extension add it.
770 					size_t found=tmp_caption.find_first_of(".");
771 					if(found!=string::npos)
772 						tmp_caption.replace(tmp_caption.begin()+found+1,tmp_caption.end(),"map");
773 					else if (tmp_caption.substr(found+1)!="map")
774 						tmp_caption.append(".map");
775 
776 					/* Create path and file in it */
777 					string path=(levels->levelpackPath+"/"+tmp_caption);
778 					if(packPath==CUSTOM_LEVELS_PATH){
779 						path=(getUserPath(USER_DATA)+"/custom/levels/"+tmp_caption);
780 					}
781 
782 					//First check if the file doesn't exist already.
783 					FILE* f;
784 					f=fopen(path.c_str(),"rb");
785 
786 					//Check if it exists.
787 					if(f){
788 						//Close the file.
789 						fclose(f);
790 
791 						//Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
792                         currentState->render(imageManager,renderer);
793                         levelEditGUIObjectRoot->render(renderer);
794 
795 						//Notify the user.
796 						msgBox(imageManager, renderer, tfm::format(_("The file %s already exists."), tmp_caption), MsgBoxOKOnly, _("Error"));
797 						return;
798 					}
799 
800 					if(!createFile(path.c_str())){
801 						cerr<<"ERROR: Unable to create level file "<<path<<endl;
802 					}else{
803 						//Update statistics.
804 						statsMgr.newAchievement("create1");
805 						if((++statsMgr.createdLevels)>=50) statsMgr.newAchievement("create50");
806 					}
807 					levels->addLevel(path);
808 					//NOTE: Also add the level to the levels levelpack in case of custom levels.
809 					if(packPath==CUSTOM_LEVELS_PATH){
810 						LevelPack* levelsPack=getLevelPackManager()->getLevelPack(LEVELS_PATH);
811 						if(levelsPack){
812 							levelsPack->addLevel(path);
813 							levelsPack->setLocked(levelsPack->getLevelCount()-1);
814 						}else{
815 							cerr<<"ERROR: Unable to add level to Levels levelpack"<<endl;
816 						}
817 					}
818 					if(packPath!=CUSTOM_LEVELS_PATH)
819 						levels->saveLevels(levels->levelpackPath+"levels.lst");
820                     refresh(imageManager, renderer);
821 
822 					//Clear the gui.
823 					if(GUIObjectRoot){
824 						delete GUIObjectRoot;
825 						GUIObjectRoot=NULL;
826 						return;
827 					}
828 				}
829 			}
830 		}
831 	}else if(name=="cfgAddCancel"){
832 		//Clear the gui.
833 		if(GUIObjectRoot){
834 			delete GUIObjectRoot;
835 			GUIObjectRoot=NULL;
836 		}
837 	}
838 
839 	//Check for move level events.
840 	if(name=="cfgMoveOK"){
841 		//Check if the entered level number is valid.
842 		//Now loop throught the children of the GUIObjectRoot in search of the fields.
843 		int level=0;
844 		int placement=0;
845 		for(unsigned int i=0;i<GUIObjectRoot->childControls.size();i++){
846 			if(GUIObjectRoot->childControls[i]->name=="MoveLevel"){
847 				level=atoi(GUIObjectRoot->childControls[i]->caption.c_str());
848 				if(level<=0 || level>levels->getLevelCount()){
849                     msgBox(imageManager,renderer,_("The entered level number isn't valid!"),MsgBoxOKOnly,_("Illegal number"));
850 					return;
851 				}
852 			}
853 			if(GUIObjectRoot->childControls[i]->name=="lstPlacement"){
854 				placement=GUIObjectRoot->childControls[i]->value;
855 			}
856 		}
857 
858 		//Now we execute the swap/move.
859 		//Check for the place before.
860 		if(placement==0){
861 			//We place the selected level before the entered level.
862 			levels->moveLevel(selectedNumber->getNumber(),level-1);
863 		}else if(placement==1){
864 			//We place the selected level after the entered level.
865 			if(level<selectedNumber->getNumber())
866 				levels->moveLevel(selectedNumber->getNumber(),level);
867 			else
868 				levels->moveLevel(selectedNumber->getNumber(),level+1);
869 		}else if(placement==2){
870 			//We swap the selected level with the entered level.
871 			levels->swapLevel(selectedNumber->getNumber(),level-1);
872 		}
873 
874 		//And save the change.
875 		if(packPath!=CUSTOM_LEVELS_PATH)
876 			levels->saveLevels(levels->levelpackPath+"/levels.lst");
877 
878         refresh(imageManager, renderer);
879 
880 		//Clear the gui.
881 		if(GUIObjectRoot){
882 			delete GUIObjectRoot;
883 			GUIObjectRoot=NULL;
884 		}
885 	}else if(name=="cfgMoveCancel"){
886 		//Clear the gui.
887 		if(GUIObjectRoot){
888 			delete GUIObjectRoot;
889 			GUIObjectRoot=NULL;
890 		}
891 	}
892 }
893