Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
149 KB
Referenced Files
None
Subscribers
None
diff --git a/src/Functions.cpp b/src/Functions.cpp
index 4075ce6..7770fed 100644
--- a/src/Functions.cpp
+++ b/src/Functions.cpp
@@ -1,1679 +1,1714 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <algorithm>
#include <SDL.h>
#include <SDL_mixer.h>
#include <SDL_syswm.h>
#include <SDL_ttf.h>
#include <string>
#include "Globals.h"
#include "Functions.h"
#include "FontManager.h"
#include "FileManager.h"
#include "GameObjects.h"
#include "LevelPack.h"
#include "TitleMenu.h"
#include "OptionsMenu.h"
#include "CreditsMenu.h"
#include "LevelEditSelect.h"
#include "LevelEditor.h"
#include "Game.h"
#include "LevelPlaySelect.h"
#include "Addons.h"
#include "InputManager.h"
#include "ImageManager.h"
#include "MusicManager.h"
#include "SoundManager.h"
#include "ScriptExecutor.h"
#include "LevelPackManager.h"
#include "ThemeManager.h"
#include "GUIListBox.h"
#include "GUIOverlay.h"
#include "StatisticsManager.h"
#include "StatisticsScreen.h"
#include "Cursors.h"
#include "ScriptAPI.h"
#include "libs/tinyformat/tinyformat.h"
#include "libs/tinygettext/tinygettext.hpp"
#include "libs/tinygettext/log.hpp"
#include "libs/findlocale/findlocale.h"
using namespace std;
#ifdef WIN32
#include <windows.h>
#include <shellapi.h>
#include <shlobj.h>
#define TO_UTF8(SRC, DEST) WideCharToMultiByte(CP_UTF8, 0, SRC, -1, DEST, sizeof(DEST), NULL, NULL)
#define TO_UTF16(SRC, DEST) MultiByteToWideChar(CP_UTF8, 0, SRC, -1, DEST, sizeof(DEST)/sizeof(DEST[0]))
#else
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#endif
//Initialise the musicManager.
//The MusicManager is used to prevent loading music files multiple times and for playing/fading music.
MusicManager musicManager;
//Initialise the soundManager.
//The SoundManager is used to keep track of the sfx in the game.
SoundManager soundManager;
//Initialise the levelPackManager.
//The LevelPackManager is used to prevent loading levelpacks multiple times and for the game to know which levelpacks there are.
LevelPackManager levelPackManager;
//Map containing changed settings using command line arguments.
map<string,string> tmpSettings;
//Pointer to the settings object.
//It is used to load and save the settings file and change the settings.
Settings* settings=nullptr;
SDL_Renderer* sdlRenderer=nullptr;
std::string pgettext(const std::string& context, const std::string& message) {
if (dictionaryManager) {
return dictionaryManager->get_dictionary().translate_ctxt(context, message);
} else {
return message;
}
}
std::string ngettext(const std::string& message,const std::string& messageplural,int num) {
if (dictionaryManager) {
return dictionaryManager->get_dictionary().translate_plural(message, messageplural, num);
} else {
//Assume it's of English plural rule
return (num != 1) ? messageplural : message;
}
}
void applySurface(int x,int y,SDL_Surface* source,SDL_Surface* dest,SDL_Rect* clip){
//The offset is needed to draw at the right location.
SDL_Rect offset;
offset.x=x;
offset.y=y;
//Let SDL do the drawing of the surface.
SDL_BlitSurface(source,clip,dest,&offset);
}
void drawRect(int x,int y,int w,int h,SDL_Renderer& renderer,Uint32 color){
//NOTE: We let SDL_gfx render it.
SDL_SetRenderDrawColor(&renderer,color >> 24,color >> 16,color >> 8,255);
//rectangleRGBA(&renderer,x,y,x+w,y+h,color >> 24,color >> 16,color >> 8,255);
const SDL_Rect r{x,y,w,h};
SDL_RenderDrawRect(&renderer,&r);
}
//Draw a box with anti-aliased borders using SDL_gfx.
void drawGUIBox(int x,int y,int w,int h,SDL_Renderer& renderer,Uint32 color){
SDL_Renderer* rd = &renderer;
//FIXME, this may get the wrong color on system with different endianness.
//Fill content's background color from function parameter
SDL_SetRenderDrawColor(rd,color >> 24,color >> 16,color >> 8,color >> 0);
{
const SDL_Rect r{x+1,y+1,w-2,h-2};
SDL_RenderFillRect(rd, &r);
}
SDL_SetRenderDrawColor(rd,0,0,0,255);
//Draw first black borders around content and leave 1 pixel in every corner
SDL_RenderDrawLine(rd,x+1,y,x+w-2,y);
SDL_RenderDrawLine(rd,x+1,y+h-1,x+w-2,y+h-1);
SDL_RenderDrawLine(rd,x,y+1,x,y+h-2);
SDL_RenderDrawLine(rd,x+w-1,y+1,x+w-1,y+h-2);
//Fill the corners with transperent color to create anti-aliased borders
SDL_SetRenderDrawColor(rd,0,0,0,160);
SDL_RenderDrawPoint(rd,x,y);
SDL_RenderDrawPoint(rd,x,y+h-1);
SDL_RenderDrawPoint(rd,x+w-1,y);
SDL_RenderDrawPoint(rd,x+w-1,y+h-1);
//Draw second lighter border around content
SDL_SetRenderDrawColor(rd,0,0,0,64);
{
const SDL_Rect r{x+1,y+1,w-2,h-2};
SDL_RenderDrawRect(rd,&r);
}
SDL_SetRenderDrawColor(rd,0,0,0,50);
//Create anti-aliasing in corners of second border
SDL_RenderDrawPoint(rd,x+1,y+1);
SDL_RenderDrawPoint(rd,x+1,y+h-2);
SDL_RenderDrawPoint(rd,x+w-2,y+1);
SDL_RenderDrawPoint(rd,x+w-2,y+h-2);
}
void drawLine(int x1,int y1,int x2,int y2,SDL_Renderer& renderer,Uint32 color){
SDL_SetRenderDrawColor(&renderer,color >> 24,color >> 16,color >> 8,255);
//NOTE: We let SDL_gfx render it.
//lineRGBA(&renderer,x1,y1,x2,y2,color >> 24,color >> 16,color >> 8,255);
SDL_RenderDrawLine(&renderer,x1,y1,x2,y2);
}
void drawLineWithArrow(int x1,int y1,int x2,int y2,SDL_Renderer& renderer,Uint32 color,int spacing,int offset,int xsize,int ysize){
//Draw line first
drawLine(x1,y1,x2,y2,renderer,color);
//calc delta and length
double dx=x2-x1;
double dy=y2-y1;
double length=sqrt(dx*dx+dy*dy);
if(length<0.001) return;
//calc the unit vector
dx/=length; dy/=length;
//Now draw arrows on it
for(double p=offset;p<length;p+=spacing){
drawLine(int(x1+p*dx+0.5),int(y1+p*dy+0.5),
int(x1+(p-xsize)*dx-ysize*dy+0.5),int(y1+(p-xsize)*dy+ysize*dx+0.5),renderer,color);
drawLine(int(x1+p*dx+0.5),int(y1+p*dy+0.5),
int(x1+(p-xsize)*dx+ysize*dy+0.5),int(y1+(p-xsize)*dy-ysize*dx+0.5),renderer,color);
}
}
ScreenData creationFailed() {
return ScreenData{ nullptr };
}
ScreenData createScreen(){
//Check if we are going fullscreen.
if(settings->getBoolValue("fullscreen"))
pickFullscreenResolution();
//Set the screen_width and height.
SCREEN_WIDTH=atoi(settings->getValue("width").c_str());
SCREEN_HEIGHT=atoi(settings->getValue("height").c_str());
//Update the camera.
camera.w=SCREEN_WIDTH;
camera.h=SCREEN_HEIGHT;
//Set the flags.
Uint32 flags = 0;
Uint32 currentFlags = SDL_GetWindowFlags(sdlWindow);
//#if !defined(ANDROID)
// flags |= SDL_DOUBLEBUF;
//#endif
if(settings->getBoolValue("fullscreen")) {
flags|=SDL_WINDOW_FULLSCREEN; //TODO with SDL2 we can also do SDL_WINDOW_FULLSCREEN_DESKTOP
}
else if(settings->getBoolValue("resizable"))
flags|=SDL_WINDOW_RESIZABLE;
//Create the window and renderer if they don't exist and check if there weren't any errors.
if (!sdlWindow && !sdlRenderer) {
SDL_CreateWindowAndRenderer(SCREEN_WIDTH, SCREEN_HEIGHT, flags, &sdlWindow, &sdlRenderer);
if(!sdlWindow || !sdlRenderer){
std::cerr << "FATAL ERROR: SDL_CreateWindowAndRenderer failed.\nError: " << SDL_GetError() << std::endl;
return creationFailed();
}
SDL_SetRenderDrawBlendMode(sdlRenderer, SDL_BlendMode::SDL_BLENDMODE_BLEND);
// White background so we see the menu on failure.
SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 255, 255);
} else if (sdlWindow) {
// Try changing to/from fullscreen
if(SDL_SetWindowFullscreen(sdlWindow, flags & SDL_WINDOW_FULLSCREEN) != 0) {
std::cerr << "WARNING: Failed to switch to fullscreen: " << SDL_GetError() << std::endl;
};
currentFlags = SDL_GetWindowFlags(sdlWindow);
// Change fullscreen resolution
if((currentFlags & SDL_WINDOW_FULLSCREEN ) || (currentFlags & SDL_WINDOW_FULLSCREEN_DESKTOP)) {
SDL_DisplayMode m{0,0,0,0,nullptr};
SDL_GetWindowDisplayMode(sdlWindow,&m);
m.w = SCREEN_WIDTH;
m.h = SCREEN_HEIGHT;
if(SDL_SetWindowDisplayMode(sdlWindow, &m) != 0) {
std::cerr << "WARNING: Failed to set display mode: " << SDL_GetError() << std::endl;
}
} else {
SDL_SetWindowSize(sdlWindow, SCREEN_WIDTH, SCREEN_HEIGHT);
}
}
//Now configure the newly created window (if windowed).
if(settings->getBoolValue("fullscreen")==false)
configureWindow();
//Set the the window caption.
SDL_SetWindowTitle(sdlWindow, ("Me and My Shadow "+version).c_str());
//FIXME Seems to be obsolete
// SDL_EnableUNICODE(1);
//Nothing went wrong so return true.
return ScreenData{sdlRenderer};
}
vector<SDL_Point> getResolutionList(){
//Vector that will hold the resolutions to choose from.
vector<SDL_Point> resolutionList;
//Enumerate available resolutions using SDL_ListModes()
//NOTE: we enumerate fullscreen resolutions because
// windowed resolutions always can be arbitrary
if(resolutionList.empty()){
// SDL_Rect **modes=SDL_ListModes(NULL,SDL_FULLSCREEN|SCREEN_FLAGS|SDL_ANYFORMAT);
//NOTe - currently only using the first display (0)
int numDisplayModes = SDL_GetNumDisplayModes(0);
if(numDisplayModes < 1){
cerr<<"ERROR: Can't enumerate available screen resolutions."
" Use predefined screen resolutions list instead."<<endl;
static const SDL_Point predefinedResolutionList[] = {
{800,600},
{1024,600},
{1024,768},
{1152,864},
{1280,720},
{1280,768},
{1280,800},
{1280,960},
{1280,1024},
{1360,768},
{1366,768},
{1440,900},
{1600,900},
{1600,1200},
{1680,1080},
{1920,1080},
{1920,1200},
{2560,1440},
{3840,2160}
};
//Fill the resolutionList.
for (unsigned int i = 0; i<sizeof(predefinedResolutionList) / sizeof(SDL_Point); i++){
resolutionList.push_back(predefinedResolutionList[i]);
}
}else{
//Fill the resolutionList.
for(int i=0;i < numDisplayModes; ++i){
SDL_DisplayMode mode;
int error = SDL_GetDisplayMode(0, i, &mode);
if(error < 0) {
//We failed to get a display mode. Should we crash here?
std::cerr << "ERROR: Failed to get display mode " << i << " " << std::endl;
}
//Check if the resolution is higher than the minimum (800x600).
if(mode.w >= 800 && mode.h >= 600){
SDL_Point res = { mode.w, mode.h };
resolutionList.push_back(res);
}
}
//Reverse it so that we begin with the lowest resolution.
reverse(resolutionList.begin(),resolutionList.end());
}
}
//Return the resolution list.
return resolutionList;
}
void pickFullscreenResolution(){
//Get the resolution list.
vector<SDL_Point> resolutionList=getResolutionList();
//The resolution that will hold the final result, we start with the minimum (800x600).
SDL_Point closestMatch = { 800, 600 };
int width=atoi(getSettings()->getValue("width").c_str());
int height=atoi(getSettings()->getValue("height").c_str());
int delta = 0x40000000;
//Now loop through the resolutionList.
for (int i = 0; i < (int)resolutionList.size(); i++){
int dx = width - resolutionList[i].x;
if (dx < 0) dx = -dx;
int dy = height - resolutionList[i].y;
if (dy < 0) dy = -dy;
if (dx + dy < delta){
delta = dx + dy;
closestMatch.x = resolutionList[i].x;
closestMatch.y = resolutionList[i].y;
}
}
//Now set the resolution to the closest match.
char s[64];
sprintf(s,"%d",closestMatch.x);
getSettings()->setValue("width",s);
sprintf(s,"%d",closestMatch.y);
getSettings()->setValue("height",s);
}
void configureWindow(){
//We only need to configure the window if it's resizable.
if(!getSettings()->getBoolValue("resizable"))
return;
//We use a new function in SDL2 to restrict minimum window size
SDL_SetWindowMinimumSize(sdlWindow, 800, 600);
}
void onVideoResize(ImageManager& imageManager, SDL_Renderer &renderer){
//Check if the resize event isn't malformed.
if(event.window.data1<=0 || event.window.data2<=0)
return;
//Check the size limit.
//TODO: SDL2 porting note: This may break on systems non-X11 or Windows systems as the window size won't be limited
//there.
if(event.window.data1<800)
event.window.data1=800;
if(event.window.data2<600)
event.window.data2=600;
//Check if it really resizes.
if(SCREEN_WIDTH==event.window.data1 && SCREEN_HEIGHT==event.window.data2)
return;
char s[32];
//Set the new width and height.
SDL_snprintf(s,32,"%d",event.window.data1);
getSettings()->setValue("width",s);
SDL_snprintf(s,32,"%d",event.window.data2);
getSettings()->setValue("height",s);
//FIXME: THIS doesn't work properly.
//Do resizing.
SCREEN_WIDTH = event.window.data1;
SCREEN_HEIGHT = event.window.data2;
//Update the camera.
camera.w=SCREEN_WIDTH;
camera.h=SCREEN_HEIGHT;
//Tell the theme to resize.
if(!loadTheme(imageManager,renderer,""))
return;
//And let the currentState update it's GUI to the new resolution.
currentState->resize(imageManager, renderer);
}
ScreenData init(){
//Initialze SDL.
if(SDL_Init(SDL_INIT_TIMER|SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_JOYSTICK)==-1) {
std::cerr << "FATAL ERROR: SDL_Init failed\nError: " << SDL_GetError() << std::endl;
return creationFailed();
}
//Initialze SDL_mixer (audio).
//Note for SDL2 port: Changed frequency from 22050 to 44100.
//22050 caused some sound artifacts on my system, and I'm not sure
//why one would use it in this day and age anyhow.
//unless it's for compatability with some legacy system.
if(Mix_OpenAudio(44100,MIX_DEFAULT_FORMAT,2,1024)==-1){
std::cerr << "FATAL ERROR: Mix_OpenAudio failed\nError: " << Mix_GetError() << std::endl;
return creationFailed();
}
//Set the volume.
Mix_Volume(-1,atoi(settings->getValue("sound").c_str()));
//Increase the number of channels.
soundManager.setNumberOfChannels(48);
//Initialze SDL_ttf (fonts).
if(TTF_Init()==-1){
std::cerr << "FATAL ERROR: TTF_Init failed\nError: " << TTF_GetError() << std::endl;
return creationFailed();
}
//Create the screen.
ScreenData screenData(createScreen());
if(!screenData) {
return creationFailed();
}
//Load key config. Then initialize joystick support.
inputMgr.loadConfig();
inputMgr.openAllJoysitcks();
//Init tinygettext for translations for the right language
dictionaryManager = new tinygettext::DictionaryManager();
dictionaryManager->set_use_fuzzy(false);
dictionaryManager->add_directory(getDataPath()+"locale");
dictionaryManager->set_charset("UTF-8");
//Disable annoying 'Couldn't translate: blah blah blah'
tinygettext::Log::set_log_info_callback(NULL);
//Check if user have defined own language. If not, find it out for the player using findlocale
string lang=getSettings()->getValue("lang");
if(lang.length()>0){
printf("Locale set by user to %s\n",lang.c_str());
language=lang;
}else{
FL_Locale *locale;
FL_FindLocale(&locale,FL_MESSAGES);
printf("Locale isn't set by user: %s\n",locale->lang);
language=locale->lang;
if(locale->country!=NULL){
language+=string("_")+string(locale->country);
}
if(locale->variant!=NULL){
language+=string("@")+string(locale->variant);
}
FL_FreeLocale(&locale);
}
//Now set the language in the dictionaryManager.
dictionaryManager->set_language(tinygettext::Language::from_name(language));
#ifdef WIN32
//Some ad-hoc fix for Windows since it accepts "zh-CN" but not "zh_CN"
std::string language2;
for (auto c : language) {
if (isalnum(c)) language2.push_back(c);
else if (c == '_') language2.push_back('-');
else break;
}
const char* languagePtr = language2.c_str();
#else
const char* languagePtr = language.c_str();
#endif
//Set time format.
setlocale(LC_TIME, languagePtr);
//Also set the numeric format for tinyformat.
tfm::setNumericFormat(
/// TRANSLATORS: This is the decimal point character in your language.
pgettext("numeric", "."),
/// TRANSLATORS: This is the thousands separator character in your language.
pgettext("numeric", ","),
/// TRANSLATORS: This is the grouping of digits in your language,
/// see <http://www.cplusplus.com/reference/locale/numpunct/grouping/> for more information.
/// However, we use string containing "123..." instead of "\x01\x02\x03...", also, "0" is the same as "".
pgettext("numeric", "3")
);
//Create the types of blocks.
for(int i=0;i<TYPE_MAX;i++){
Game::blockNameMap[Game::blockName[i]]=i;
}
//Structure that holds the event type/name pair.
struct EventTypeName{
int type;
const char* name;
};
//Create the types of game object event types.
{
const EventTypeName types[]={
{GameObjectEvent_PlayerWalkOn,"playerWalkOn"},
{GameObjectEvent_PlayerIsOn,"playerIsOn"},
{GameObjectEvent_PlayerLeave,"playerLeave"},
{GameObjectEvent_OnCreate,"onCreate"},
{GameObjectEvent_OnEnterFrame,"onEnterFrame"},
{ GameObjectEvent_OnPlayerInteraction, "onPlayerInteraction" },
{GameObjectEvent_OnToggle,"onToggle"},
{GameObjectEvent_OnSwitchOn,"onSwitchOn"},
{GameObjectEvent_OnSwitchOff,"onSwitchOff"},
{0,NULL}
};
for(int i=0;types[i].name;i++){
Game::gameObjectEventNameMap[types[i].name]=types[i].type;
Game::gameObjectEventTypeMap[types[i].type]=types[i].name;
}
}
//Create the types of level event types.
{
const EventTypeName types[]={
{LevelEvent_OnCreate,"onCreate"},
{LevelEvent_OnSave,"onSave"},
{LevelEvent_OnLoad,"onLoad"},
{0,NULL}
};
for(int i=0;types[i].name;i++){
Game::levelEventNameMap[types[i].name]=types[i].type;
Game::levelEventTypeMap[types[i].type]=types[i].name;
}
}
//Nothing went wrong so we return true.
return screenData;
}
bool loadFonts(){
//Load the fonts.
//NOTE: This is a separate method because it will be called separately when re-initing in case of language change.
//NOTE2: Since the font fallback is implemented, the font will not be loaded again if call loadFonts() twice.
if (fontMgr) {
return true;
}
fontMgr = new FontManager;
fontMgr->loadFonts();
fontTitle = fontMgr->getFont("fontTitle");
fontGUI = fontMgr->getFont("fontGUI");
fontGUISmall = fontMgr->getFont("fontGUISmall");
fontText = fontMgr->getFont("fontText");
fontMono = fontMgr->getFont("fontMono");
if (fontTitle == NULL || fontGUI == NULL || fontGUISmall == NULL || fontText == NULL || fontMono == NULL){
printf("FATAL ERROR: Unable to load fonts!\n");
return false;
}
//Nothing went wrong so return true.
return true;
}
//Generate small arrows used for some GUI widgets.
static void generateArrows(SDL_Renderer& renderer){
TTF_Font* fontArrow = fontMgr->getFont("fontArrow");
arrowLeft1=textureFromText(renderer,*fontArrow,"<",objThemes.getTextColor(false));
arrowRight1=textureFromText(renderer,*fontArrow,">",objThemes.getTextColor(false));
arrowLeft2=textureFromText(renderer,*fontArrow,"<",objThemes.getTextColor(true));
arrowRight2=textureFromText(renderer,*fontArrow,">",objThemes.getTextColor(true));
}
bool loadTheme(ImageManager& imageManager,SDL_Renderer& renderer,std::string name){
//Load default fallback theme if it isn't loaded yet
if(objThemes.themeCount()==0){
if(objThemes.appendThemeFromFile(getDataPath()+"themes/Cloudscape/theme.mnmstheme", imageManager, renderer)==NULL){
printf("ERROR: Can't load default theme file\n");
return false;
}
}
//Resize background or load specific theme
bool success=true;
if(name==""||name.empty()){
objThemes.scaleToScreen();
}else{
string theme=processFileName(name);
if(objThemes.appendThemeFromFile(theme+"/theme.mnmstheme", imageManager, renderer)==NULL){
printf("ERROR: Can't load theme %s\n",theme.c_str());
success=false;
}
}
generateArrows(renderer);
//Everything went fine so return true.
return success;
}
static SDL_Cursor* loadCursor(const char* image[]){
int i,row,col;
//The array that holds the data (0=white 1=black)
Uint8 data[4*32];
//The array that holds the alpha mask (0=transparent 1=visible)
Uint8 mask[4*32];
//The coordinates of the hotspot of the cursor.
int hotspotX, hotspotY;
i=-1;
//Loop through the rows and columns.
//NOTE: We assume a cursor size of 32x32.
for(row=0;row<32;++row){
for(col=0; col<32;++col){
if(col % 8) {
data[i]<<=1;
mask[i]<<=1;
}else{
++i;
data[i]=mask[i]=0;
}
switch(image[4+row][col]){
case '+':
data[i] |= 0x01;
mask[i] |= 0x01;
break;
case '.':
mask[i] |= 0x01;
break;
default:
break;
}
}
}
//Get the hotspot x and y locations from the last line of the cursor.
sscanf(image[4+row],"%d,%d",&hotspotX,&hotspotY);
return SDL_CreateCursor(data,mask,32,32,hotspotX,hotspotY);
}
bool loadFiles(ImageManager& imageManager, SDL_Renderer& renderer){
//Load the fonts.
if(!loadFonts())
return false;
//Show a loading screen
{
int w = 0,h = 0;
SDL_GetRendererOutputSize(&renderer, &w, &h);
SDL_Color fg={255,255,255,0};
TexturePtr loadingTexture = titleTextureFromText(renderer, _("Loading..."), fg, w);
SDL_Rect loadingRect = rectFromTexture(*loadingTexture);
loadingRect.x = (w-loadingRect.w)/2;
loadingRect.y = (h-loadingRect.h)/2;
SDL_RenderCopy(sdlRenderer, loadingTexture.get(), NULL, &loadingRect);
SDL_RenderPresent(sdlRenderer);
SDL_RenderClear(sdlRenderer);
}
musicManager.destroy();
//Load the music and play it.
if(musicManager.loadMusic((getDataPath()+"music/menu.music")).empty()){
printf("WARNING: Unable to load background music! \n");
}
musicManager.playMusic("menu",false);
//Load all the music lists from the data and user data path.
{
vector<string> musicLists=enumAllFiles((getDataPath()+"music/"),"list",true);
for(unsigned int i=0;i<musicLists.size();i++)
getMusicManager()->loadMusicList(musicLists[i]);
musicLists=enumAllFiles((getUserPath(USER_DATA)+"music/"),"list",true);
for(unsigned int i=0;i<musicLists.size();i++)
getMusicManager()->loadMusicList(musicLists[i]);
}
//Set the list to the configured one.
getMusicManager()->setMusicList(getSettings()->getValue("musiclist"));
//Check if music is enabled.
if(getSettings()->getBoolValue("music"))
getMusicManager()->setEnabled();
//Load the sound effects
soundManager.loadSound((getDataPath()+"sfx/jump.wav").c_str(),"jump");
soundManager.loadSound((getDataPath()+"sfx/hit.wav").c_str(),"hit");
soundManager.loadSound((getDataPath()+"sfx/checkpoint.wav").c_str(),"checkpoint");
soundManager.loadSound((getDataPath()+"sfx/swap.wav").c_str(),"swap");
soundManager.loadSound((getDataPath()+"sfx/toggle.ogg").c_str(),"toggle");
soundManager.loadSound((getDataPath()+"sfx/error.wav").c_str(),"error");
soundManager.loadSound((getDataPath()+"sfx/collect.wav").c_str(),"collect");
soundManager.loadSound((getDataPath()+"sfx/achievement.ogg").c_str(),"achievement");
//Load the cursor images from the Cursor.h file.
cursors[CURSOR_POINTER]=loadCursor(pointer);
cursors[CURSOR_CARROT]=loadCursor(ibeam);
cursors[CURSOR_DRAG]=loadCursor(closedhand);
cursors[CURSOR_SIZE_HOR]=loadCursor(size_hor);
cursors[CURSOR_SIZE_VER]=loadCursor(size_ver);
cursors[CURSOR_SIZE_FDIAG]=loadCursor(size_fdiag);
cursors[CURSOR_SIZE_BDIAG]=loadCursor(size_bdiag);
cursors[CURSOR_REMOVE]=loadCursor(remove_cursor);
cursors[CURSOR_POINTING_HAND] = loadCursor(pointing_hand);
//Set the default cursor right now.
SDL_SetCursor(cursors[CURSOR_POINTER]);
levelPackManager.destroy();
//Now sum up all the levelpacks.
vector<string> v=enumAllDirs(getDataPath()+"levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getDataPath()+"levelpacks/"+*i);
}
v=enumAllDirs(getUserPath(USER_DATA)+"levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getUserPath(USER_DATA)+"levelpacks/"+*i);
}
v=enumAllDirs(getUserPath(USER_DATA)+"custom/levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getUserPath(USER_DATA)+"custom/levelpacks/"+*i);
}
//Now we add a special levelpack that will contain the levels not in a levelpack.
LevelPack* levelsPack=new LevelPack;
levelsPack->levelpackName="Levels";
levelsPack->levelpackPath=LEVELS_PATH;
levelsPack->type=COLLECTION;
LevelPack* customLevelsPack=new LevelPack;
customLevelsPack->levelpackName="Custom Levels";
customLevelsPack->levelpackPath=CUSTOM_LEVELS_PATH;
customLevelsPack->type=COLLECTION;
//List the main levels and add them one for one.
v = enumAllFiles(getDataPath() + "levels/");
for (vector<string>::iterator i = v.begin(); i != v.end(); ++i){
levelsPack->addLevel(getDataPath() + "levels/" + *i);
levelsPack->setLocked(levelsPack->getLevelCount() - 1);
}
//List the addon levels and add them one for one.
v=enumAllFiles(getUserPath(USER_DATA)+"levels/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelsPack->addLevel(getUserPath(USER_DATA)+"levels/"+*i);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
}
//List the custom levels and add them one for one.
v=enumAllFiles(getUserPath(USER_DATA)+"custom/levels/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelsPack->addLevel(getUserPath(USER_DATA)+"custom/levels/"+*i);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
customLevelsPack->addLevel(getUserPath(USER_DATA)+"custom/levels/"+*i);
customLevelsPack->setLocked(customLevelsPack->getLevelCount()-1);
}
//Add them to the manager.
levelPackManager.addLevelPack(levelsPack);
levelPackManager.addLevelPack(customLevelsPack);
//Load statistics
statsMgr.loadPicture(renderer, imageManager);
statsMgr.registerAchievements(imageManager);
statsMgr.loadFile(getUserPath(USER_CONFIG)+"statistics");
//Do something ugly and slow
statsMgr.reloadCompletedLevelsAndAchievements();
statsMgr.reloadOtherAchievements();
//Load the theme, both menu and default.
//NOTE: Loading theme may fail and returning false would stop everything, default theme will be used instead.
if (!loadTheme(imageManager,renderer,getSettings()->getValue("theme"))){
getSettings()->setValue("theme","%DATA%/themes/Cloudscape");
saveSettings();
}
//Nothing failed so return true.
return true;
}
bool loadSettings(){
//Check the version of config file.
int version = 0;
std::string cfgV05 = getUserPath(USER_CONFIG) + "meandmyshadow_V0.5.cfg";
std::string cfgV04 = getUserPath(USER_CONFIG) + "meandmyshadow.cfg";
if (fileExists(cfgV05.c_str())) {
//We find a config file of current version.
version = 0x000500;
} else if (fileExists(cfgV04.c_str())) {
//We find a config file of V0.4 version or earlier.
copyFile(cfgV04.c_str(), cfgV05.c_str());
version = 0x000400;
} else {
//No config file found, just create a new one.
version = 0x000500;
}
settings=new Settings(cfgV05);
settings->parseFile(version);
//Now apply settings changed through command line arguments, if any.
map<string,string>::iterator it;
for(it=tmpSettings.begin();it!=tmpSettings.end();++it){
settings->setValue(it->first,it->second);
}
tmpSettings.clear();
//Always return true?
return true;
}
bool saveSettings(){
return settings->save();
}
Settings* getSettings(){
return settings;
}
MusicManager* getMusicManager(){
return &musicManager;
}
SoundManager* getSoundManager(){
return &soundManager;
}
LevelPackManager* getLevelPackManager(){
return &levelPackManager;
}
void flipScreen(SDL_Renderer& renderer){
// Render the data from the back buffer.
SDL_RenderPresent(&renderer);
}
void clean(){
//Save statistics
statsMgr.saveFile(getUserPath(USER_CONFIG)+"statistics");
//We delete the settings.
if(settings){
delete settings;
settings=NULL;
}
//Delete dictionaryManager.
delete dictionaryManager;
//Get rid of the currentstate.
//NOTE: The state is probably already deleted by the changeState function.
if(currentState)
delete currentState;
//Destroy the GUI if present.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//These calls to destroy makes sure stuff is
//deleted before SDL is uninitialised (as these managers are stack allocated
//globals.)
//Destroy the musicManager.
musicManager.destroy();
//Destroy all sounds
soundManager.destroy();
//Destroy the cursors.
for(int i=0;i<CURSOR_MAX;i++){
SDL_FreeCursor(cursors[i]);
cursors[i]=NULL;
}
//Destroy the levelPackManager.
levelPackManager.destroy();
levels=NULL;
//Close all joysticks.
inputMgr.closeAllJoysticks();
//Close the fonts and quit SDL_ttf.
delete fontMgr;
fontMgr = NULL;
TTF_Quit();
//Remove the temp surface.
SDL_DestroyRenderer(sdlRenderer);
SDL_DestroyWindow(sdlWindow);
arrowLeft1.reset(nullptr);
arrowLeft2.reset(nullptr);
arrowRight1.reset(nullptr);
arrowRight2.reset(nullptr);
//Stop audio.and quit
Mix_CloseAudio();
//SDL2 porting note. Not sure why this was only done on apple.
//#ifndef __APPLE__
Mix_Quit();
//#endif
//And finally quit SDL.
SDL_Quit();
}
void setNextState(int newstate){
//Only change the state when we aren't already exiting.
if(nextState!=STATE_EXIT){
nextState=newstate;
}
}
void changeState(ImageManager& imageManager, SDL_Renderer& renderer, int fade){
//Check if there's a nextState.
if(nextState!=STATE_NULL){
//Fade out, if fading is enabled.
if (currentState && settings->getBoolValue("fading")) {
for (; fade >= 0; fade -= 17) {
currentState->render(imageManager, renderer);
//TODO: Shouldn't the gamestate take care of rendering the GUI?
if (GUIObjectRoot) GUIObjectRoot->render(renderer);
dimScreen(renderer, static_cast<Uint8>(255 - fade));
//draw new achievements (if any) as overlay
statsMgr.render(imageManager, renderer);
flipScreen(renderer);
SDL_Delay(1000/FPS);
}
}
//Delete the currentState.
delete currentState;
currentState=NULL;
//Set the currentState to the nextState.
stateID=nextState;
nextState=STATE_NULL;
//Init the state.
switch(stateID){
case STATE_GAME:
{
currentState=NULL;
Game* game=new Game(renderer, imageManager);
currentState=game;
//Check if we should load record file or a level.
if(!Game::recordFile.empty()){
if (Game::recordFile[0] == '?') {
//This means load record file with current version of level.
game->loadRecord(imageManager, renderer, Game::recordFile.c_str() + 1, levels->getLevelFile().c_str());
} else {
game->loadRecord(imageManager, renderer, Game::recordFile.c_str());
}
Game::recordFile.clear();
}else{
game->loadLevel(imageManager,renderer,levels->getLevelFile());
levels->saveLevelProgress();
}
}
break;
case STATE_MENU:
currentState=new Menu(imageManager, renderer);
break;
case STATE_LEVEL_SELECT:
currentState=new LevelPlaySelect(imageManager, renderer);
break;
case STATE_LEVEL_EDIT_SELECT:
currentState=new LevelEditSelect(imageManager, renderer);
break;
case STATE_LEVEL_EDITOR:
{
currentState=NULL;
LevelEditor* levelEditor=new LevelEditor(renderer, imageManager);
currentState=levelEditor;
//Load the selected level.
levelEditor->loadLevel(imageManager,renderer,levels->getLevelFile());
}
break;
case STATE_OPTIONS:
currentState=new Options(imageManager, renderer);
break;
case STATE_ADDONS:
currentState=new Addons(renderer, imageManager);
break;
case STATE_CREDITS:
currentState=new Credits(imageManager,renderer);
break;
case STATE_STATISTICS:
currentState=new StatisticsScreen(imageManager,renderer);
break;
}
//NOTE: STATE_EXIT isn't mentioned, meaning that currentState is null.
//This way the game loop will break and the program will exit.
}
}
void musicStoppedHook(){
//We just call the musicStopped method of the MusicManager.
musicManager.musicStopped();
}
void channelFinishedHook(int channel){
soundManager.channelFinished(channel);
}
bool checkCollision(const SDL_Rect& a,const SDL_Rect& b){
//Check if the left side of box a isn't past the right side of b.
if(a.x>=b.x+b.w){
return false;
}
//Check if the right side of box a isn't left of the left side of b.
if(a.x+a.w<=b.x){
return false;
}
//Check if the top side of box a isn't under the bottom side of b.
if(a.y>=b.y+b.h){
return false;
}
//Check if the bottom side of box a isn't above the top side of b.
if(a.y+a.h<=b.y){
return false;
}
//We have collision.
return true;
}
bool pointOnRect(const SDL_Rect& point, const SDL_Rect& rect) {
if(point.x >= rect.x && point.x < rect.x + rect.w
&& point.y >= rect.y && point.y < rect.y + rect.h) {
return true;
}
return false;
}
int parseArguments(int argc, char** argv){
//Loop through all arguments.
//We start at one since 0 is the command itself.
for(int i=1;i<argc;i++){
string argument=argv[i];
//Check if the argument is the data-dir.
if(argument=="--data-dir"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Configure the dataPath with the given path.
dataPath=argv[i];
if(!getDataPath().empty()){
char c=dataPath[dataPath.size()-1];
if(c!='/'&&c!='\\') dataPath+="/";
}
}else if(argument=="--user-dir"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Configure the userPath with the given path.
userPath=argv[i];
if(!userPath.empty()){
char c=userPath[userPath.size()-1];
if(c!='/'&&c!='\\') userPath+="/";
}
}else if(argument=="-f" || argument=="-fullscreen" || argument=="--fullscreen"){
tmpSettings["fullscreen"]="1";
}else if(argument=="-w" || argument=="-windowed" || argument=="--windowed"){
tmpSettings["fullscreen"]="0";
}else if(argument=="-mv" || argument=="-music" || argument=="--music"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Now set the music volume.
tmpSettings["music"]=argv[i];
}else if(argument=="-sv" || argument=="-sound" || argument=="--sound"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Now set sound volume.
tmpSettings["sound"]=argv[i];
}else if(argument=="-set" || argument=="--set"){
//We need a second and a third argument so we increase i.
i+=2;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//And set the setting.
tmpSettings[argv[i-1]]=argv[i];
}else if(argument=="-v" || argument=="-version" || argument=="--version"){
//Print the version.
printf("%s\n",version.c_str());
return 0;
}else if(argument=="-h" || argument=="-help" || argument=="--help"){
//If the help is requested we'll return false without printing an error.
//This way the usage/help text will be printed.
return -1;
}else{
//Any other argument is unknow so we return false.
printf("ERROR: Unknown argument %s\n\n",argument.c_str());
return -1;
}
}
//If everything went well we can return true.
return 1;
}
//Special structure that will recieve the GUIEventCallbacks of the messagebox.
struct msgBoxHandler:public GUIEventCallback{
public:
//Integer containing the ret(urn) value of the messageBox.
int ret;
public:
//Constructor.
msgBoxHandler():ret(0){}
void GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
//Make sure it's a click event.
if(eventType==GUIEventClick){
//Set the return value.
ret=obj->value;
//After a click event we can delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
}
};
msgBoxResult msgBox(ImageManager& imageManager,SDL_Renderer& renderer, const string& prompt,msgBoxButtons buttons,const string& title){
//Create the event handler.
msgBoxHandler objHandler;
//Create the GUIObjectRoot, the height and y location is temp.
//It depends on the content what it will be.
GUIObject* root=new GUIFrame(imageManager,renderer,(SCREEN_WIDTH-600)/2,200,600,200,title.c_str());
//Integer containing the current y location used to grow dynamic depending on the content.
int y=50;
//Now process the prompt.
{
//NOTE: We shouldn't modify the cotents in the c_str() of a string,
//since it's said that at least in g++ the std::string is copy-on-write
//hence if we modify the content it may break
//The copy of the prompt.
std::vector<char> copyOfPrompt(prompt.begin(), prompt.end());
//Append another '\0' to it.
copyOfPrompt.push_back(0);
//Pointer to the string.
char* lps = &(copyOfPrompt[0]);
//Pointer to a character.
char* lp=NULL;
//The list of labels.
std::vector<GUIObject*> labels;
//We keep looping forever.
//The only way out is with the break statement.
for(;;){
//As long as it's still the same sentence we continue.
//It will stop when there's a newline or end of line.
for(lp=lps;*lp!='\n'&&*lp!='\r'&&*lp!=0;lp++);
//Store the character we stopped on. (End or newline)
char c=*lp;
//Set the character in the string to 0, making lps a string containing one sentence.
*lp=0;
//Add a GUIObjectLabel with the sentence.
GUIObject *label = new GUILabel(imageManager, renderer, 0, y, root->width, 25, lps, 0, true, true, GUIGravityCenter);
labels.push_back(label);
root->addChild(label);
//Calculate the width of the text.
int w = 0;
TTF_SizeUTF8(fontText, lps, &w, NULL);
w += 20;
if (w > root->width) root->width = w;
//Increase y with 25, about the height of the text.
y+=25;
//Check the stored character if it was a stop.
if(c==0){
//It was so break out of the for loop.
lps=lp;
break;
}
//It wasn't meaning more will follow.
//We set lps to point after the "newline" forming a new string.
lps=lp+1;
}
//Shrink the dialog if it's too big.
if (root->width > SCREEN_WIDTH - 20) root->width = SCREEN_WIDTH - 20;
root->left = (SCREEN_WIDTH - root->width) / 2;
//Move labels to their correct locations.
for (auto label : labels) {
label->width = root->width;
}
}
//Add 70 to y to leave some space between the content and the buttons.
y+=70;
//Recalc the size of the message box.
root->top=(SCREEN_HEIGHT-y)/2;
root->height=y;
//Now we need to add the buttons.
//Integer containing the number of buttons to add.
int count=0;
//Array with the return codes for the buttons.
int value[3]={0};
//Array containing the captation for the buttons.
string button[3]={"","",""};
switch(buttons){
case MsgBoxOKCancel:
count=2;
button[0]=_("OK");value[0]=MsgBoxOK;
button[1]=_("Cancel");value[1]=MsgBoxCancel;
break;
case MsgBoxAbortRetryIgnore:
count=3;
button[0]=_("Abort");value[0]=MsgBoxAbort;
button[1]=_("Retry");value[1]=MsgBoxRetry;
button[2]=_("Ignore");value[2]=MsgBoxIgnore;
break;
case MsgBoxYesNoCancel:
count=3;
button[0]=_("Yes");value[0]=MsgBoxYes;
button[1]=_("No");value[1]=MsgBoxNo;
button[2]=_("Cancel");value[2]=MsgBoxCancel;
break;
case MsgBoxYesNo:
count=2;
button[0]=_("Yes");value[0]=MsgBoxYes;
button[1]=_("No");value[1]=MsgBoxNo;
break;
case MsgBoxRetryCancel:
count=2;
button[0]=_("Retry");value[0]=MsgBoxRetry;
button[1]=_("Cancel");value[1]=MsgBoxCancel;
break;
default:
count=1;
button[0]=_("OK");value[0]=MsgBoxOK;
break;
}
//Now we start making the buttons.
{
//Reduce y so that the buttons fit inside the frame.
y-=40;
double places[3]={0.0};
if(count==1){
places[0]=0.5;
}else if(count==2){
places[0]=0.35;
places[1]=0.65;
}else if(count==3){
places[0]=0.25;
places[1]=0.5;
places[2]=0.75;
}
std::vector<GUIButton*> buttons;
//Loop to add the buttons.
for(int i=0;i<count;i++){
GUIButton* obj = new GUIButton(imageManager, renderer, root->width*places[i], y, -1, 36, button[i].c_str(), value[i], true, true, GUIGravityCenter);
obj->eventCallback=&objHandler;
buttons.push_back(obj);
root->addChild(obj);
}
//Update widgets
for (int i = 0; i < count; i++) {
buttons[i]->render(renderer, 0, 0, false);
}
bool overlap = false;
//Check if they overlap
if (buttons[0]->left - buttons[0]->gravityX < 5 ||
buttons[count - 1]->left - buttons[count - 1]->gravityX + buttons[count - 1]->width > root->width - 5)
{
overlap = true;
} else {
for (int i = 0; i < count - 1; i++) {
if (buttons[i]->left - buttons[i]->gravityX + buttons[i]->width >= buttons[i + 1]->left - buttons[i + 1]->gravityX) {
overlap = true;
break;
}
}
}
//Shrink the font size if any buttons are overlap
if (overlap) {
for (int i = 0; i < count; i++) {
buttons[i]->smallFont = true;
buttons[i]->width = -1;
}
}
}
//Now we dim the screen and keep the GUI rendering/updating.
GUIOverlay* overlay=new GUIOverlay(renderer,root);
overlay->keyboardNavigationMode = LeftRightFocus | UpDownFocus | TabFocus | ((count == 1) ? 0 : ReturnControls);
overlay->enterLoop(imageManager, renderer, true, count == 1);
//And return the result.
return (msgBoxResult)objHandler.ret;
}
// A helper function to read a character from utf8 string
// s: the string
// p [in,out]: the position
// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
int utf8ReadForward(const char* s, int& p) {
int ch = (unsigned char)s[p];
if (ch < 0x80){
if (ch) p++;
return ch;
} else if (ch < 0xC0){
// skip invalid characters
while (((unsigned char)s[p] & 0xC0) == 0x80) p++;
return -1;
} else if (ch < 0xE0){
int c2 = (unsigned char)s[++p];
if ((c2 & 0xC0) != 0x80) return -1;
ch = ((ch & 0x1F) << 6) | (c2 & 0x3F);
p++;
return ch;
} else if (ch < 0xF0){
int c2 = (unsigned char)s[++p];
if ((c2 & 0xC0) != 0x80) return -1;
int c3 = (unsigned char)s[++p];
if ((c3 & 0xC0) != 0x80) return -1;
ch = ((ch & 0xF) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);
p++;
return ch;
} else if (ch < 0xF8){
int c2 = (unsigned char)s[++p];
if ((c2 & 0xC0) != 0x80) return -1;
int c3 = (unsigned char)s[++p];
if ((c3 & 0xC0) != 0x80) return -1;
int c4 = (unsigned char)s[++p];
if ((c4 & 0xC0) != 0x80) return -1;
ch = ((ch & 0x7) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F);
if (ch >= 0x110000) ch = -1;
p++;
return ch;
} else {
p++;
return -1;
}
}
// A helper function to read a character backward from utf8 string (experimental)
// s: the string
// p [in,out]: the position
// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
int utf8ReadBackward(const char* s, int& p) {
if (p <= 0) return 0;
do {
p--;
} while (p > 0 && ((unsigned char)s[p] & 0xC0) == 0x80);
int tmp = p;
return utf8ReadForward(s, tmp);
}
#ifndef WIN32
// ad-hoc function to check if a program is installed
static bool programExists(const std::string& program) {
std::string p = tfm::format("which \"%s\" 2>&1", program);
const int BUFSIZE = 128;
char buf[BUFSIZE];
FILE *fp;
if ((fp = popen(p.c_str(), "r")) == NULL) {
return false;
}
while (fgets(buf, BUFSIZE, fp) != NULL) {
// Drop all outputs since 'which' returns -1 when the program is not found
}
if (pclose(fp)) {
return false;
}
return true;
}
#endif
void openWebsite(const std::string& url) {
#ifdef WIN32
wchar_t ws[4096];
TO_UTF16(url.c_str(), ws);
SDL_SysWMinfo info = {};
SDL_VERSION(&info.version);
SDL_GetWindowWMInfo(sdlWindow, &info);
ShellExecuteW(info.info.win.window, L"open", ws, NULL, NULL, SW_SHOW);
#else
static int method = -1;
// Some of these methods are copied from https://stackoverflow.com/questions/5116473/
const char* methods[] = {
"xdg-open", "xdg-open \"%s\"",
"gnome-open", "gnome-open \"%s\"",
"kde-open", "kde-open \"%s\"",
"open", "open \"%s\"",
"python", "python -m webbrowser \"%s\"",
"sensible-browser", "sensible-browser \"%s\"",
"x-www-browser", "x-www-browser \"%s\"",
NULL,
};
if (method < 0) {
for (method = 0; methods[method]; method += 2) {
if (programExists(methods[method])) break;
}
}
if (methods[method]) {
std::string p = tfm::format(methods[method + 1], url);
system(p.c_str());
} else {
fprintf(stderr, "TODO: openWebsite is not implemented on your system\n");
}
#endif
}
std::string appendURLToLicense(const std::string& license) {
// if the license doesn't include url, try to detect it from a predefined list
if (license.find("://") == std::string::npos) {
std::string normalized;
for (char c : license) {
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')) {
normalized.push_back(c);
} else if (c >= 'a' && c <= 'z') {
normalized.push_back(c + ('A' - 'a'));
}
}
const char* licenses[] = {
// AGPL
"AGPL1", "AGPLV1", NULL, "http://www.affero.org/oagpl.html",
"AGPL2", "AGPLV2", NULL, "http://www.affero.org/agpl2.html",
"AGPL", NULL, "https://gnu.org/licenses/agpl.html",
// LGPL
"LGPL21", "LGPLV21", NULL, "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html",
"LGPL2", "LGPLV2", NULL, "https://www.gnu.org/licenses/old-licenses/lgpl-2.0.html",
"LGPL", NULL, "https://www.gnu.org/copyleft/lesser.html",
// GPL
"GPL1", "GPLV1", NULL, "https://www.gnu.org/licenses/old-licenses/gpl-1.0.html",
"GPL2", "GPLV2", NULL, "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html",
"GPL", NULL, "https://gnu.org/licenses/gpl.html",
// CC BY-NC-ND
"CCBYNCND1", "CCBYNDNC1", NULL, "https://creativecommons.org/licenses/by-nd-nc/1.0",
"CCBYNCND25", "CCBYNDNC25", NULL, "https://creativecommons.org/licenses/by-nc-nd/2.5",
"CCBYNCND2", "CCBYNDNC2", NULL, "https://creativecommons.org/licenses/by-nc-nd/2.0",
"CCBYNCND3", "CCBYNDNC3", NULL, "https://creativecommons.org/licenses/by-nc-nd/3.0",
"CCBYNCND", "CCBYNDNC", NULL, "https://creativecommons.org/licenses/by-nc-nd/4.0",
// CC BY-NC-SA
"CCBYNCSA1", NULL, "https://creativecommons.org/licenses/by-nc-sa/1.0",
"CCBYNCSA25", NULL, "https://creativecommons.org/licenses/by-nc-sa/2.5",
"CCBYNCSA2", NULL, "https://creativecommons.org/licenses/by-nc-sa/2.0",
"CCBYNCSA3", NULL, "https://creativecommons.org/licenses/by-nc-sa/3.0",
"CCBYNCSA", NULL, "https://creativecommons.org/licenses/by-nc-sa/4.0",
// CC BY-ND
"CCBYND1", NULL, "https://creativecommons.org/licenses/by-nd/1.0",
"CCBYND25", NULL, "https://creativecommons.org/licenses/by-nd/2.5",
"CCBYND2", NULL, "https://creativecommons.org/licenses/by-nd/2.0",
"CCBYND3", NULL, "https://creativecommons.org/licenses/by-nd/3.0",
"CCBYND", NULL, "https://creativecommons.org/licenses/by-nd/4.0",
// CC BY-NC
"CCBYNC1", NULL, "https://creativecommons.org/licenses/by-nc/1.0",
"CCBYNC25", NULL, "https://creativecommons.org/licenses/by-nc/2.5",
"CCBYNC2", NULL, "https://creativecommons.org/licenses/by-nc/2.0",
"CCBYNC3", NULL, "https://creativecommons.org/licenses/by-nc/3.0",
"CCBYNC", NULL, "https://creativecommons.org/licenses/by-nc/4.0",
// CC BY-SA
"CCBYSA1", NULL, "https://creativecommons.org/licenses/by-sa/1.0",
"CCBYSA25", NULL, "https://creativecommons.org/licenses/by-sa/2.5",
"CCBYSA2", NULL, "https://creativecommons.org/licenses/by-sa/2.0",
"CCBYSA3", NULL, "https://creativecommons.org/licenses/by-sa/3.0",
"CCBYSA", NULL, "https://creativecommons.org/licenses/by-sa/4.0",
// CC BY
"CCBY1", NULL, "https://creativecommons.org/licenses/by/1.0",
"CCBY25", NULL, "https://creativecommons.org/licenses/by/2.5",
"CCBY2", NULL, "https://creativecommons.org/licenses/by/2.0",
"CCBY3", NULL, "https://creativecommons.org/licenses/by/3.0",
"CCBY", NULL, "https://creativecommons.org/licenses/by/4.0",
// CC0
"CC0", NULL, "https://creativecommons.org/publicdomain/zero/1.0",
// WTFPL
"WTFPL", NULL, "http://www.wtfpl.net/",
// end
NULL,
};
for (int i = 0; licenses[i]; i++) {
bool found = false;
for (; licenses[i]; i++) {
if (normalized.find(licenses[i]) != std::string::npos) found = true;
}
i++;
if (found) {
return license + tfm::format(" <%s>", licenses[i]);
}
}
}
return license;
}
int getKeyboardRepeatDelay() {
static int ret = -1;
if (ret < 0) {
#ifdef WIN32
int i = 0;
SystemParametersInfoW(SPI_GETKEYBOARDDELAY, 0, &i, 0);
// NOTE: these weird numbers are derived from Microsoft's documentation explaining the return value of SystemParametersInfo.
i = clamp(i, 0, 3);
ret = (i + 1) * 10;
#else
// TODO: platform-dependent code
// Assume it's 250ms, i.e. 10 frames
ret = 10;
#endif
// Debug
#ifdef _DEBUG
printf("getKeyboardRepeatDelay() = %d\n", ret);
#endif
}
return ret;
}
int getKeyboardRepeatInterval() {
static int ret = -1;
if (ret < 0) {
#ifdef WIN32
int i = 0;
SystemParametersInfoW(SPI_GETKEYBOARDSPEED, 0, &i, 0);
// NOTE: these weird numbers are derived from Microsoft's documentation explaining the return value of SystemParametersInfo.
i = clamp(i, 0, 31);
ret = (int)floor(40.0f / (2.5f + 0.887097f * (float)i) + 0.5f);
#else
// TODO: platform-dependent code
// Assume it's 25ms, i.e. 1 frame
ret = 1;
#endif
// Debug
#ifdef _DEBUG
printf("getKeyboardRepeatInterval() = %d\n", ret);
#endif
}
return ret;
}
std::string escapeNewline(const std::string& src) {
std::string ret;
for (auto c : src) {
switch (c) {
case '\r':
break;
case '\n':
ret += "\\n";
break;
case '\\':
ret += "\\\\";
break;
default:
ret.push_back(c);
break;
}
}
return ret;
}
std::string unescapeNewline(const std::string& src) {
std::string ret;
for (int i = 0, m = src.size(); i < m; i++) {
char c = src[i];
if (c == '\\' && i + 1 < m) {
i++;
char c2 = src[i];
switch (c2) {
case 'r':
break;
case 'n':
ret.push_back('\n');
break;
case '\\':
ret.push_back('\\');
break;
default:
ret.push_back(c);
ret.push_back(c2);
break;
}
} else {
ret.push_back(c);
}
}
return ret;
}
+
+std::string escapeCString(const std::string& src) {
+ std::string ret;
+ for (auto c : src) {
+ switch (c) {
+ case '\r':
+ break;
+ case '\n':
+ ret += "\\n";
+ break;
+ case '\t':
+ ret += "\\t";
+ break;
+ case '\0':
+ ret += "\\0";
+ break;
+ case '\\':
+ ret += "\\\\";
+ break;
+ case '\"':
+ ret += "\\\"";
+ break;
+ default:
+ if (c >= 0 && c < 32) {
+ char s[8];
+ sprintf(s, "\\x%02X", (int)c);
+ ret += s;
+ } else {
+ ret.push_back(c);
+ }
+ break;
+ }
+ }
+ return ret;
+}
diff --git a/src/Functions.h b/src/Functions.h
index 727e889..92b468a 100644
--- a/src/Functions.h
+++ b/src/Functions.h
@@ -1,306 +1,309 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FUNCTIONS_H
#define FUNCTIONS_H
#include "Settings.h"
#include "Globals.h"
#include <SDL.h>
#include <string>
#include <vector>
class MusicManager;
class SoundManager;
//gettext function
//message: The message to translate.
//Returns: The translated string or the original string if there is not translation available.
#define _(message) (dictionaryManager!=NULL?dictionaryManager->get_dictionary().translate(message).c_str():std::string(message).c_str())
//gettext function
//NOTE: "_C" is conflict to some Android macros so we change its name.
//dictionaryManager: Pointer to the dictionaryManager to use for the translation.
//message: The message to translate.
//Returns: The translated string or the original string if there is not translation available.
#define _CC(dictionaryManager, message) ((dictionaryManager)!=NULL?(dictionaryManager)->get_dictionary().translate(message).c_str():std::string(message).c_str())
//dummy function for xgettext
//message: The message to translate.
//Returns: message parameter
#define __(message) (message)
class ImageManager;
struct SDL_Texture;
class LevelPackManager;
//gettext function for contexts
//context: The context of the message.
//message: The message to translate.
//Returns: The translated string or the original string if there are no translations available.
std::string pgettext(const std::string& context, const std::string& message);
//gettext function for plural forms
//message: The singular version of the message to translate.
//messageplural: The plural version of the message to translate.
//num: The number to fetch the plural form for
//Returns: The translated string or the original string if there are no translations available.
std::string ngettext(const std::string& message, const std::string& messageplural, int num);
//Method for drawing an SDL_Surface onto another.
//x: The x location to draw the source on the desination.
//y: The y location to draw the source on the desination.
//source: The SDL_Surface to draw.
//dest: The SDL_Surface to draw on.
//clip: Rectangle which part of the source should be drawn.
void applySurface(int x,int y,SDL_Surface* source,SDL_Surface* dest,SDL_Rect* clip);
//Method used to draw an rectangle.
//x: The top left x location of the rectangle.
//y: The top left y location of the rectangle.
//w: The width of the rectangle,
//h: The height of the rectangle.
//dest: The SDL_Surface to draw on.
//color: The color of the rectangle border to draw.
void drawRect(int x, int y, int w, int h, SDL_Renderer &renderer, Uint32 color=0);
//Method used to draw filled boxes with an anti-alliased border.
//Mostly used for GUI components.
//x: The top left x location of the box.
//y: The top left y location of the box.
//w: The width of the box,
//h: The height of the box.
//renderer: The SDL_Renderer to render on..
//color: The color of the rectangle background to draw.
void drawGUIBox(int x,int y,int w,int h,SDL_Renderer& renderer,Uint32 color);
//Method used to draw a line.
//x1: The x location of the start point.
//y1: The y location of the start point.
//x2: The x location of the end point.
//y2: The y location of the end point.
//dest: The SDL_Surface to draw on.
//color: The color of the line to draw.
void drawLine(int x1,int y1,int x2,int y2,SDL_Renderer &renderer,Uint32 color=0);
//Method used to draw a line with some arrows on it.
//x1: The x location of the start point.
//y1: The y location of the start point.
//x2: The x location of the end point.
//y2: The y location of the end point.
//dest: The SDL_Surface to draw on.
//color: The color of the line to draw.
//spacing: The spacing between arrows.
//offset: Offset of first arrow relative to the start point.
//xize, ysize: The size of arrow.
void drawLineWithArrow(int x1, int y1, int x2, int y2, SDL_Renderer &renderer, Uint32 color=0, int spacing=16, int offset=0, int xsize=5, int ysize=5);
//Method that will load the fonts needed for the game.
//NOTE: It's separate from loadFiles(), since it might get called separatly from the code when changing the language.
bool loadFonts();
//Method that will load the default theme again.
//name: name of the theme to load or empty for scaling background
//NOTE: It's separate from loadFiles(), since it might get called separatly from the code when changing resolution.
bool loadTheme(ImageManager& imageManager, SDL_Renderer& renderer, std::string name);
struct ScreenData{
SDL_Renderer* renderer;
explicit operator bool() const {
return renderer!=nullptr;
}
};
//This method will attempt to create the screen/window.
//NOTE: It's separate from init(), since it might get called separatly from the code when changing resolution.
ScreenData createScreen();
//Method for retrieving a list of resolutions.
std::vector<SDL_Point> getResolutionList();
//Method that is called when a fullscreen window is created.
//It will choose the resolution that is closest to the configured one.
void pickFullscreenResolution();
//This method is used to configure the window that is created by createScreen.
//NOTE: It will do it in a WM specific way, so if the wm is unkown it will do nothing.
void configureWindow();
//Call this method when receive SDL_VIDEORESIZE event.
void onVideoResize(ImageManager &imageManager, SDL_Renderer& renderer);
//Initialises the game. This is done almost at the beginning of the program.
//It initialises: SDL, SDL_Mixer, SDL_ttf, the screen and the block types.
//Returns: True if everything goes well.
ScreenData init();
//Loads some important files, like the background music and the default theme.
//Returns: True if everything goes well.
bool loadFiles(ImageManager &imageManager, SDL_Renderer &renderer);
//This method will load the settings from the settings file.
//Returns: False if there's an error while loading.
bool loadSettings();
//This method will save the settings to the settings file.
//Returns: False if there's an error while saving.
bool saveSettings();
//Method used to get a pointer to the settings object.
//Returns: A pointer to the settings object.
Settings* getSettings();
//Method used to get a pointer to the MusicManager object.
//Returns: A pointer to the MusicManager object.
MusicManager* getMusicManager();
//Method used to get a pointer to the SoundManager object.
//Returns: A pointer to the SoundManager object.
SoundManager* getSoundManager();
//Method used to get a pointer to the LevelPackManager object.
//Returns: A pointer to the LevelPackManager object.
LevelPackManager* getLevelPackManager();
//Method that will, depending on the rendering backend, draw the screen surface to the screen.
void flipScreen(SDL_Renderer& renderer);
//Method used to clean up before quiting meandmyshadow.
void clean();
//Sets what the nextState will be.
//newstate: Integer containing the id of the newstate.
void setNextState(int newstate);
//Method that will perform the state change.
//It will fade out (but not fade in).
void changeState(ImageManager &imageManager, SDL_Renderer &renderer, int fade = 255);
//This method is called when music is stopped.
//NOTE: This method is outside the MusicManager because it can't be called otherwise by SDL_Mixer.
//Do not call this method anywhere in the code!
void musicStoppedHook();
//This method is called when a sfx finished playing.
//NOTE: This method is outside the SoundManager because it can't be called otherwise by SDL_Mixer.
//Do not call this method anywhere in the code!
//channel: The number of the channel that is finished.
void channelFinishedHook(int channel);
//Checks collision between two SDL_Rects.
//a: The first rectangle.
//b: The second rectangle.
//Returns: True if the two rectangles collide.
bool checkCollision(const SDL_Rect& a,const SDL_Rect& b);
//Checks if a given point lays on an SDL_Rect
//point: The point to check.
//rect: The rectangle to check.
//Returns: True if the point is on the rectangle.
bool pointOnRect(const SDL_Rect& a,const SDL_Rect& b);
//Parse the commandline arguments.
//argc: Integer containing the number of aruguments there are.
//argv: The arguments.
//Returns: -1 if something goes wrong while parsing,
// 0 if version is shown,
// 1 if everything is alright
int parseArguments(int argc, char** argv);
//From http://en.wikipedia.org/wiki/Clamping_(graphics)
//x: The value to clamp.
//min: The minimum x can be.
//max: The maximum x can be.
//Returns: Integer containing the clamped value.
int inline clamp(int x,int min,int max){
return (x>max)?max:(x<min)?min:x;
}
//Enumeration containing the different messagebox button combinations.
enum msgBoxButtons{
//Only one button with the text OK.
MsgBoxOKOnly=0,
//Two buttons, one saying OK, the other Cancel.
MsgBoxOKCancel=1,
//Three buttons, Abort, Retry, Ignore.
MsgBoxAbortRetryIgnore=2,
//Three buttons, Yes, No or Cancel.
MsgBoxYesNoCancel=3,
//Two buttons, one saying Yes, the other No.
MsgBoxYesNo=4,
//Two buttons, one saying Retry, the other Cancel.
MsgBoxRetryCancel=5,
};
//Enumeration containing the different result that can be retrieved from a messagebox.
//It represents the button that has been pressed.
enum msgBoxResult{
//The OK button.
MsgBoxOK=1,
//The cancel button.
MsgBoxCancel=2,
//The abort button.
MsgBoxAbort=3,
//The retry button.
MsgBoxRetry=4,
//The ignore button.
MsgBoxIgnore=5,
//The yes button.
MsgBoxYes=6,
//The no button.
MsgBoxNo=7,
};
//Method that prompts the user with a notification and/or question.
//prompt: The message the user is prompted with.
//buttons: Which buttons the messagebox should have.
//title: The title of the message box.
//Returns: A msgBoxResult which button has been pressed.
msgBoxResult msgBox(ImageManager& imageManager,SDL_Renderer& renderer, const std::string& prompt,msgBoxButtons buttons,const std::string& title);
// A helper function to read a character from utf8 string
// s: the string
// p [in,out]: the position
// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
int utf8ReadForward(const char* s, int& p);
// A helper function to read a character backward from utf8 string (experimental)
// s: the string
// p [in,out]: the position
// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
int utf8ReadBackward(const char* s, int& p);
// Open the website using default web browser.
// url: The url of the website. Currently only http and https are supported.
// Also we assume that the url only contains ASCII characters.
// WARNING: Passing other url may result in arbitrary behavior (esp. passing '*.exe' on Windows).
void openWebsite(const std::string& url);
// Append a URL to a license if the license doesn't include URL (try to detect it from a predefined list).
// license: The license.
// Return value: The license appended with a URL if we detect the license successfully.
std::string appendURLToLicense(const std::string& license);
// Retrieves the (approximate) keyboard repeat delay time, in frames (NOTE: frame rate is hardcoded as 40).
int getKeyboardRepeatDelay();
// Retrieves the (approximate) keyboard repeat interval time, in frames (NOTE: frame rate is hardcoded as 40).
int getKeyboardRepeatInterval();
// Escape the newline '\n' and '\\', and removes '\r'.
std::string escapeNewline(const std::string& src);
// Unescape the newline '\n' and '\\'.
std::string unescapeNewline(const std::string& src);
+// Removes '\r' and escape the characters which are invalid in C string, e.g. '\n', '\\', '\"', etc.
+std::string escapeCString(const std::string& src);
+
#endif
diff --git a/src/ITreeStorage.h b/src/ITreeStorage.h
index f6c7ff1..9c10467 100644
--- a/src/ITreeStorage.h
+++ b/src/ITreeStorage.h
@@ -1,86 +1,107 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ITREESTORAGE_H
#define ITREESTORAGE_H
#include <string>
#include <vector>
//An abstract class which is used to transfer the data from file to TreeStorage.
//NOTE: Usually you should simply use the TreeStorageNode class.
//You only need to use this class when you want to store the data in a customized way.
//Another use case is you only want to read some of the property in the file, by using early exit feature.
class ITreeStorageBuilder{
+public:
+ //Represents a position in a text file.
+ struct FilePosition {
+ int row;
+ int column;
+ inline void advance() {
+ column++;
+ }
+ inline void advanceByCharacter(int c) {
+ if (c == '\n') {
+ row++;
+ column = 1;
+ } else if (c == '\t') {
+ //Assume tab width is 4
+ column = ((column + 3) & (-4)) + 1;
+ } else if (c >= 0 && c <= 255) {
+ //TODO: Unicode support
+ column++;
+ }
+ }
+ };
public:
//Destructor.
virtual ~ITreeStorageBuilder(){}
//Set the name of the TreeStorageNode.
//name: The name to give.
//return value: true means early exit, i.e. doesn't read the file further.
- virtual bool setName(std::string& name)=0;
+ virtual bool setName(const std::string& name, const FilePosition& pos) = 0;
//Set the value of the TreeStorageNode.
//value: The value to give.
//return value: true means early exit, i.e. doesn't read the file further.
- virtual bool setValue(std::vector<std::string>& value) = 0;
+ virtual bool setValue(const std::vector<std::string>& value, const std::vector<FilePosition>& pos) = 0;
//Method that should create a new node in the TreeStorageNode and add it to it's subnodes.
//Returns a pointer to the new TreeStorageNode. NULL means early exit, i.e. doesn't read the file further.
virtual ITreeStorageBuilder* newNode()=0;
//Method that should add a new attribute to the TreeStorageNode.
//name: The name of the new attribute.
//value: The value(s) of the new attribute.
//return value: true means early exit, i.e. doesn't read the file further.
- virtual bool newAttribute(std::string& name, std::vector<std::string>& value) = 0;
+ virtual bool newAttribute(const std::string& name, const std::vector<std::string>& value, const FilePosition& namePos, const std::vector<FilePosition>& valuePos) = 0;
};
//An abstract class which is used to transfer the data from TreeStorage to file.
//NOTE: Usually you should simply use the TreeStorageNode class.
//You only need to use this class when you want to store the data in a customized way.
class ITreeStorageReader{
public:
//Destructor.
virtual ~ITreeStorageReader(){}
//Sets the parameter name to the name of the TreeStorageNode.
//name: The string to fill with the name;
virtual void getName(std::string& name)=0;
//Sets the parameter value to the value(s) of the TreeStorageNode.
//value: The vector to fill with the value(s);
virtual void getValue(std::vector<std::string>& value)=0;
//Method used for iterating through the attributes of the TreeStorageNode.
//pUserData: A user pointer, usually stores information about the iterator itself. NULL means get the first attribute.
//name: The string fill with the name of the attribute.
//value: Vector to fill with the value(s) of the attribute.
//return value: The new value of the user pointer. NULL means there are no more attributes.
virtual void* getNextAttribute(void* pUserData,std::string& name,std::vector<std::string>& value)=0;
//Method used for iterating through the subnodes of the TreeStorageNode.
//pUserData: A user pointer, usually stores information about the iterator itself. NULL means get the first node.
//obj: Pointer that will be pointed to the nextNode, if present.
//return value: The new value of the user pointer. NULL means there are no more nodes.
virtual void* getNextNode(void* pUserData,ITreeStorageReader*& obj)=0;
};
#endif
diff --git a/src/LevelEditSelect.cpp b/src/LevelEditSelect.cpp
index e79a0c6..fb2edef 100644
--- a/src/LevelEditSelect.cpp
+++ b/src/LevelEditSelect.cpp
@@ -1,892 +1,955 @@
/*
* Copyright (C) 2012-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "LevelEditSelect.h"
#include "GameState.h"
#include "Functions.h"
#include "FileManager.h"
#include "Globals.h"
#include "GUIObject.h"
#include "GUIListBox.h"
#include "GUIScrollBar.h"
#include "GUISpinBox.h"
#include "InputManager.h"
#include "StatisticsManager.h"
#include "Game.h"
#include "GUIOverlay.h"
+#include "LevelPackPOTExporter.h"
#include <algorithm>
#include <string>
#include <iostream>
#include "libs/tinyformat/tinyformat.h"
using namespace std;
LevelEditSelect::LevelEditSelect(ImageManager& imageManager, SDL_Renderer& renderer):LevelSelect(imageManager,renderer,_("Map Editor"),LevelPackManager::CUSTOM_PACKS){
//Create the gui.
createGUI(imageManager,renderer, true);
//Set the levelEditGUIObjectRoot.
levelEditGUIObjectRoot=GUIObjectRoot;
//show level list
changePack();
refresh(imageManager, renderer);
}
LevelEditSelect::~LevelEditSelect(){
selectedNumber=NULL;
}
void LevelEditSelect::createGUI(ImageManager& imageManager,SDL_Renderer &renderer, bool initial){
if(initial){
//The levelpack name text field.
levelpackName=new GUITextBox(imageManager,renderer,280,104,240,32);
levelpackName->eventCallback=this;
levelpackName->visible=false;
GUIObjectRoot->addChild(levelpackName);
//Create the six buttons at the bottom of the screen.
newPack = new GUIButton(imageManager, renderer, 0, 0, -1, 32, _("New Levelpack"));
newPack->name = "cmdNewLvlpack";
newPack->eventCallback = this;
GUIObjectRoot->addChild(newPack);
propertiesPack = new GUIButton(imageManager, renderer, 0, 0, -1, 32, _("Pack Properties"), 0, true, true, GUIGravityCenter);
propertiesPack->name = "cmdLvlpackProp";
propertiesPack->eventCallback = this;
GUIObjectRoot->addChild(propertiesPack);
removePack = new GUIButton(imageManager, renderer, 0, 0, -1, 32, _("Remove Pack"), 0, true, true, GUIGravityRight);
removePack->name = "cmdRmLvlpack";
removePack->eventCallback = this;
GUIObjectRoot->addChild(removePack);
move = new GUIButton(imageManager, renderer, 0, 0, -1, 32, _("Move Map"));
move->name = "cmdMoveMap";
move->eventCallback = this;
//NOTE: Set enabled equal to the inverse of initial.
//When resizing the window initial will be false and therefor the move button can stay enabled.
move->enabled = false;
GUIObjectRoot->addChild(move);
remove = new GUIButton(imageManager, renderer, 0, 0, -1, 32, _("Remove Map"), 0, false, true, GUIGravityCenter);
remove->name = "cmdRmMap";
remove->eventCallback = this;
GUIObjectRoot->addChild(remove);
edit = new GUIButton(imageManager, renderer, 0, 0, -1, 32, _("Edit Map"), 0, false, true, GUIGravityRight);
edit->name = "cmdEdit";
edit->eventCallback = this;
GUIObjectRoot->addChild(edit);
}
//Move buttons to default position
const int x1 = int(SCREEN_WIDTH*0.02), x2 = int(SCREEN_WIDTH*0.5), x3 = int(SCREEN_WIDTH*0.98);
const int y1 = SCREEN_HEIGHT - 120, y2 = SCREEN_HEIGHT - 60;
newPack->left = x1; newPack->top = y1; newPack->gravity = GUIGravityLeft;
propertiesPack->left = x2; propertiesPack->top = y1; propertiesPack->gravity = GUIGravityCenter;
removePack->left = x3; removePack->top = y1; removePack->gravity = GUIGravityRight;
move->left = x1; move->top = y2; move->gravity = GUIGravityLeft;
remove->left = x2; remove->top = y2; remove->gravity = GUIGravityCenter;
edit->left = x3; edit->top = y2; edit->gravity = GUIGravityRight;
isVertical = false;
//Reset the font size
newPack->smallFont = false; newPack->width = -1;
propertiesPack->smallFont = false; propertiesPack->width = -1;
removePack->smallFont = false; removePack->width = -1;
move->smallFont = false; move->width = -1;
remove->smallFont = false; remove->width = -1;
edit->smallFont = false; edit->width = -1;
//Now update widgets and then check if they overlap
GUIObjectRoot->render(renderer, 0, 0, false);
if (propertiesPack->left - propertiesPack->gravityX < newPack->left + newPack->width ||
propertiesPack->left - propertiesPack->gravityX + propertiesPack->width > removePack->left - removePack->gravityX)
{
newPack->smallFont = true; newPack->width = -1;
propertiesPack->smallFont = true; propertiesPack->width = -1;
removePack->smallFont = true; removePack->width = -1;
move->smallFont = true; move->width = -1;
remove->smallFont = true; remove->width = -1;
edit->smallFont = true; edit->width = -1;
}
// NOTE: the following code is necessary (e.g. for Germany)
//Check again
GUIObjectRoot->render(renderer, 0, 0, false);
if (propertiesPack->left - propertiesPack->gravityX < newPack->left + newPack->width ||
propertiesPack->left - propertiesPack->gravityX + propertiesPack->width > removePack->left - removePack->gravityX)
{
newPack->left = SCREEN_WIDTH*0.02;
newPack->top = SCREEN_HEIGHT - 140;
newPack->smallFont = false;
newPack->width = -1;
newPack->gravity = GUIGravityLeft;
propertiesPack->left = SCREEN_WIDTH*0.02;
propertiesPack->top = SCREEN_HEIGHT - 100;
propertiesPack->smallFont = false;
propertiesPack->width = -1;
propertiesPack->gravity = GUIGravityLeft;
removePack->left = SCREEN_WIDTH*0.02;
removePack->top = SCREEN_HEIGHT - 60;
removePack->smallFont = false;
removePack->width = -1;
removePack->gravity = GUIGravityLeft;
move->left = SCREEN_WIDTH*0.98;
move->top = SCREEN_HEIGHT - 140;
move->smallFont = false;
move->width = -1;
move->gravity = GUIGravityRight;
remove->left = SCREEN_WIDTH*0.98;
remove->top = SCREEN_HEIGHT - 100;
remove->smallFont = false;
remove->width = -1;
remove->gravity = GUIGravityRight;
edit->left = SCREEN_WIDTH*0.98;
edit->top = SCREEN_HEIGHT - 60;
edit->smallFont = false;
edit->width = -1;
edit->gravity = GUIGravityRight;
isVertical = true;
}
}
void LevelEditSelect::changePack(){
packPath = levelpacks->item[levelpacks->value].first;
packName = levelpacks->item[levelpacks->value].second;
if(packPath==CUSTOM_LEVELS_PATH){
//Disable some levelpack buttons.
propertiesPack->enabled=false;
removePack->enabled=false;
}else{
//Enable some levelpack buttons.
propertiesPack->enabled=true;
removePack->enabled=true;
}
//Set last levelpack.
getSettings()->setValue("lastlevelpack",levelpacks->getName());
//Now let levels point to the right pack.
levels=getLevelPackManager()->getLevelPack(levelpacks->getName());
//invalidate the tooltip
toolTip.number = -1;
}
void LevelEditSelect::packProperties(ImageManager& imageManager,SDL_Renderer& renderer, bool newPack){
+ packPropertiesFrames.clear();
+
//Open a message popup.
- GUIObject* root=new GUIFrame(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-390)/2,600,390,_("Properties"));
- GUIObject* obj;
+ GUIObject *root = new GUIFrame(imageManager, renderer, (SCREEN_WIDTH - 600) / 2, (SCREEN_HEIGHT - 390) / 2, 600, 390,
+ newPack ? _("New Levelpack") : "");
+ GUIObject *obj;
+
+ if (newPack) {
+ packPropertiesFrames.resize(1);
+ } else {
+ packPropertiesFrames.resize(2);
+
+ GUISingleLineListBox *sllb = new GUISingleLineListBox(imageManager, renderer, 40, 12, 520, 36);
+ sllb->name = "sllb";
+ sllb->addItem("Properties", _("Properties"));
+ sllb->addItem("Tools", _("Tools"));
+ sllb->value = 0;
+ sllb->eventCallback = this;
+ root->addChild(sllb);
+ }
obj=new GUILabel(imageManager,renderer,40,50,240,36,_("Name:"));
root->addChild(obj);
+ packPropertiesFrames[0].push_back(obj);
obj=new GUITextBox(imageManager,renderer,60,80,480,36,packName.c_str());
if(newPack)
obj->caption="";
obj->name="LvlpackName";
root->addChild(obj);
-
+ packPropertiesFrames[0].push_back(obj);
+
obj=new GUILabel(imageManager,renderer,40,120,240,36,_("Description:"));
root->addChild(obj);
+ packPropertiesFrames[0].push_back(obj);
obj=new GUITextBox(imageManager,renderer,60,150,480,36,levels->levelpackDescription.c_str());
if(newPack)
obj->caption="";
obj->name="LvlpackDescription";
root->addChild(obj);
-
+ packPropertiesFrames[0].push_back(obj);
+
obj=new GUILabel(imageManager,renderer,40,190,240,36,_("Congratulation text:"));
root->addChild(obj);
-
+ packPropertiesFrames[0].push_back(obj);
+
obj=new GUITextBox(imageManager,renderer,60,220,480,36,levels->congratulationText.c_str());
if(newPack)
obj->caption="";
obj->name="LvlpackCongratulation";
root->addChild(obj);
+ packPropertiesFrames[0].push_back(obj);
obj = new GUILabel(imageManager, renderer, 40, 260, 240, 36, _("Music list:"));
root->addChild(obj);
+ packPropertiesFrames[0].push_back(obj);
obj = new GUITextBox(imageManager, renderer, 60, 290, 480, 36, levels->levelpackMusicList.c_str());
if (newPack)
obj->caption = "";
obj->name = "LvlpackMusic";
root->addChild(obj);
+ packPropertiesFrames[0].push_back(obj);
obj=new GUIButton(imageManager,renderer,root->width*0.3,390-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgOK";
obj->eventCallback=this;
root->addChild(obj);
+ packPropertiesFrames[0].push_back(obj);
GUIButton *cancelButton = new GUIButton(imageManager, renderer, root->width*0.7, 390 - 44, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
cancelButton->name = "cfgCancel";
cancelButton->eventCallback = this;
root->addChild(cancelButton);
+ packPropertiesFrames[0].push_back(cancelButton);
+
+ if (!newPack) {
+ GUIButton *btn = new GUIButton(imageManager, renderer, root->width*0.5, 80, -1, 36, _("Export translation template"), 0, true, false, GUIGravityCenter);
+ btn->name = "cfgExportPOT";
+ btn->smallFont = true;
+ btn->eventCallback = this;
+ root->addChild(btn);
+ packPropertiesFrames[1].push_back(btn);
+
+ obj = new GUIButton(imageManager, renderer, root->width*0.5, 390 - 44, -1, 36, _("Close"), 0, true, false, GUIGravityCenter);
+ obj->name = "cfgCancel";
+ obj->eventCallback = this;
+ root->addChild(obj);
+ packPropertiesFrames[1].push_back(obj);
+ }
//Create the gui overlay.
//NOTE: We don't need to store a pointer since it will auto cleanup itself.
new AddonOverlay(renderer, root, cancelButton, NULL, UpDownFocus | TabFocus | ReturnControls | LeftRightControls);
if(newPack){
packPath.clear();
packName.clear();
}
}
void LevelEditSelect::addLevel(ImageManager& imageManager,SDL_Renderer& renderer){
//Open a message popup.
GUIObject* root=new GUIFrame(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-200)/2,600,200,_("Add level"));
GUIObject* obj;
obj=new GUILabel(imageManager,renderer,40,80,240,36,_("File name:"));
root->addChild(obj);
char s[64];
SDL_snprintf(s,64,"map%02d.map",levels->getLevelCount()+1);
obj=new GUITextBox(imageManager,renderer,300,80,240,36,s);
obj->name="LvlFile";
root->addChild(obj);
obj=new GUIButton(imageManager,renderer,root->width*0.3,200-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgAddOK";
obj->eventCallback=this;
root->addChild(obj);
GUIButton *cancelButton = new GUIButton(imageManager, renderer, root->width*0.7, 200 - 44, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
cancelButton->name = "cfgAddCancel";
cancelButton->eventCallback = this;
root->addChild(cancelButton);
//Dim the screen using the tempSurface.
//NOTE: We don't need to store a pointer since it will auto cleanup itself.
new AddonOverlay(renderer, root, cancelButton, NULL, UpDownFocus | TabFocus | ReturnControls | LeftRightControls);
}
void LevelEditSelect::moveLevel(ImageManager& imageManager,SDL_Renderer& renderer){
//Open a message popup.
GUIObject* root=new GUIFrame(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-200)/2,600,200,_("Move level"));
GUIObject* obj;
obj=new GUILabel(imageManager,renderer,40,60,240,36,_("Level: "));
root->addChild(obj);
GUISpinBox *spinBox = new GUISpinBox(imageManager, renderer, 300, 60, 240, 36);
spinBox->caption = tfm::format("%d", selectedNumber->getNumber() + 1);
spinBox->format = "%1.0f";
spinBox->limitMin = 1.0f;
spinBox->limitMax = float(levels->getLevelCount());
spinBox->name = "MoveLevel";
root->addChild(spinBox);
obj=new GUISingleLineListBox(imageManager,renderer,root->width*0.5,110,240,36,true,true,GUIGravityCenter);
obj->name="lstPlacement";
vector<string> v;
v.push_back(_("Before"));
v.push_back(_("After"));
v.push_back(_("Swap"));
(dynamic_cast<GUISingleLineListBox*>(obj))->addItems(v);
obj->value=0;
root->addChild(obj);
obj=new GUIButton(imageManager,renderer,root->width*0.3,200-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgMoveOK";
obj->eventCallback=this;
root->addChild(obj);
GUIButton *cancelButton = new GUIButton(imageManager, renderer, root->width*0.7, 200 - 44, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
cancelButton->name = "cfgMoveCancel";
cancelButton->eventCallback = this;
root->addChild(cancelButton);
//Create the gui overlay.
//NOTE: We don't need to store a pointer since it will auto cleanup itself.
new AddonOverlay(renderer, root, cancelButton, NULL, TabFocus | ReturnControls | LeftRightControls);
}
void LevelEditSelect::refresh(ImageManager& imageManager, SDL_Renderer& renderer, bool change){
int m=levels->getLevelCount();
if(change){
numbers.clear();
//clear the selected level
if(selectedNumber!=NULL){
selectedNumber=NULL;
}
//Disable the level specific buttons.
move->enabled=false;
remove->enabled=false;
edit->enabled=false;
for(int n=0;n<=m;n++){
numbers.emplace_back(imageManager, renderer);
}
}
for(int n=0;n<m;n++){
SDL_Rect box={(n%LEVELS_PER_ROW)*64+80,(n/LEVELS_PER_ROW)*64+184,0,0};
numbers[n].init(renderer,n,box);
}
SDL_Rect box={(m%LEVELS_PER_ROW)*64+80,(m/LEVELS_PER_ROW)*64+184,0,0};
numbers[m].init(renderer,"+",box,m);
m++; //including the "+" button
if(m>LEVELS_DISPLAYED_IN_SCREEN){
levelScrollBar->maxValue=(m-LEVELS_DISPLAYED_IN_SCREEN+LEVELS_PER_ROW-1)/LEVELS_PER_ROW;
levelScrollBar->visible=true;
}else{
levelScrollBar->maxValue=0;
levelScrollBar->visible=false;
}
if (levels->levelpackPath == LEVELS_PATH || levels->levelpackPath == CUSTOM_LEVELS_PATH)
levelpackDescription->caption = _("Individual levels which are not contained in any level packs");
else if (!levels->levelpackDescription.empty())
levelpackDescription->caption = _CC(levels->getDictionaryManager(), levels->levelpackDescription);
else
levelpackDescription->caption = "";
//invalidate the tooltip
toolTip.number = -1;
}
void LevelEditSelect::selectNumber(ImageManager& imageManager, SDL_Renderer& renderer, unsigned int number, bool selected){
if (selected) {
if (number >= 0 && number < levels->getLevelCount()) {
levels->setCurrentLevel(number);
setNextState(STATE_LEVEL_EDITOR);
} else {
addLevel(imageManager, renderer);
}
}else{
move->enabled = false;
remove->enabled = false;
edit->enabled = false;
selectedNumber = NULL;
if (number == numbers.size() - 1){
if (isKeyboardOnly) {
selectedNumber = &numbers[number];
} else {
addLevel(imageManager, renderer);
}
} else if (number >= 0 && number < levels->getLevelCount()) {
selectedNumber=&numbers[number];
//Enable the level specific buttons.
//NOTE: We check if 'remove levelpack' is enabled, if not then it's the Levels levelpack.
if(removePack->enabled)
move->enabled=true;
remove->enabled=true;
edit->enabled=true;
}
}
}
void LevelEditSelect::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
//Call handleEvents() of base class.
LevelSelect::handleEvents(imageManager, renderer);
if (section == 3) {
//Check focus movement
if (inputMgr.isKeyDownEvent(INPUTMGR_RIGHT)){
isKeyboardOnly = true;
section2 += isVertical ? 3 : 1;
} else if (inputMgr.isKeyDownEvent(INPUTMGR_LEFT)){
isKeyboardOnly = true;
section2 -= isVertical ? 3 : 1;
} else if (inputMgr.isKeyDownEvent(INPUTMGR_UP)){
isKeyboardOnly = true;
section2 -= isVertical ? 1 : 3;
} else if (inputMgr.isKeyDownEvent(INPUTMGR_DOWN)){
isKeyboardOnly = true;
section2 += isVertical ? 1 : 3;
}
if (section2 > 6) section2 -= 6;
else if (section2 < 1) section2 += 6;
//Check if enter is pressed
if (isKeyboardOnly && inputMgr.isKeyDownEvent(INPUTMGR_SELECT) && section2 >= 1 && section2 <= 6) {
GUIButton *buttons[6] = {
newPack, propertiesPack, removePack, move, remove, edit
};
GUIEventCallback_OnEvent(imageManager, renderer, buttons[section2 - 1]->name, buttons[section2 - 1], GUIEventClick);
}
}
}
void LevelEditSelect::render(ImageManager& imageManager,SDL_Renderer &renderer){
//Let the levelselect render.
LevelSelect::render(imageManager,renderer);
//Draw highlight in keyboard only mode.
if (isKeyboardOnly) {
GUIButton *buttons[6] = {
newPack, propertiesPack, removePack, move, remove, edit
};
for (int i = 0; i < 6; i++) {
buttons[i]->state = (section == 3 && section2 - 1 == i) ? 1 : 0;
}
}
}
void LevelEditSelect::resize(ImageManager& imageManager, SDL_Renderer &renderer){
//Let the levelselect resize.
LevelSelect::resize(imageManager, renderer);
//Create the GUI.
createGUI(imageManager,renderer, false);
//NOTE: This is a workaround for buttons failing when resizing.
if(packPath==CUSTOM_LEVELS_PATH){
removePack->enabled=false;
propertiesPack->enabled=false;
}
if(selectedNumber)
selectNumber(imageManager, renderer, selectedNumber->getNumber(),false);
}
void LevelEditSelect::renderTooltip(SDL_Renderer& renderer,unsigned int number,int dy){
if (!toolTip.name || toolTip.number != number) {
SDL_Color fg = objThemes.getTextColor(true);
toolTip.number = number;
if (number < (unsigned int)levels->getLevelCount()){
//Render the name of the level.
toolTip.name = textureFromText(renderer, *fontText, _CC(levels->getDictionaryManager(), levels->getLevelName(number)), fg);
} else {
//Add level button
toolTip.name = textureFromText(renderer, *fontText, _("Add level"), fg);
}
}
//Check if name isn't null.
if(!toolTip.name)
return;
//Now draw a square the size of the three texts combined.
SDL_Rect r=numbers[number].box;
r.y-=dy*64;
const SDL_Rect nameSize = rectFromTexture(*toolTip.name);
r.w=nameSize.w;
r.h=nameSize.h;
//Make sure the tooltip doesn't go outside the window.
if(r.y>SCREEN_HEIGHT-200){
r.y-=nameSize.h+4;
}else{
r.y+=numbers[number].box.h+2;
}
if(r.x+r.w>SCREEN_WIDTH-50)
r.x=SCREEN_WIDTH-50-r.w;
//Draw a rectange
Uint32 color=0xFFFFFFFF;
drawGUIBox(r.x-5,r.y-5,r.w+10,r.h+10,renderer,color);
//Calc the position to draw.
SDL_Rect r2=r;
//Now we render the name if the surface isn't null.
if(toolTip.name){
//Draw the name.
applyTexture(r2.x, r2.y, toolTip.name, renderer);
}
}
//Escape invalid characters in a file name (mainly for Windows).
static std::string escapeFileName(const std::string& fileName) {
std::string ret;
for (int i = 0, m = fileName.size(); i < m; i++) {
bool escape = false;
char c = fileName[i];
switch (c) {
case '\"': case '*': case '/': case ':': case '<':
case '>': case '?': case '\\': case '|': case '%':
escape = true;
break;
}
if (c <= 0x1F || c >= 0x7F) escape = true;
if (i == 0 || i == m - 1) {
switch (c) {
case ' ': case '.':
escape = true;
break;
}
}
if (escape) {
ret += "%" + tfm::format("%02X", (int)(unsigned char)c);
} else {
ret.push_back(c);
}
}
return ret;
}
void LevelEditSelect::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
//NOTE: We check for the levelpack change to enable/disable some levelpack buttons.
if(name=="cmdLvlPack"){
//We call changepack and return to prevent the LevelSelect to undo what we did.
changePack();
refresh(imageManager, renderer);
return;
}
//Let the level select handle his GUI events.
LevelSelect::GUIEventCallback_OnEvent(imageManager,renderer,name,obj,eventType);
//Check for the edit button.
if(name=="cmdNewLvlpack"){
//Create a new pack.
packProperties(imageManager,renderer, true);
}else if(name=="cmdLvlpackProp"){
//Show the pack properties.
packProperties(imageManager,renderer, false);
}else if(name=="cmdRmLvlpack"){
//Show an "are you sure" message.
if(msgBox(imageManager,renderer,tfm::format(_("Are you sure remove the level pack '%s'?"),packName),MsgBoxYesNo,_("Remove prompt"))==MsgBoxYes){
//Remove the directory.
if(!removeDirectory(levels->levelpackPath.c_str())){
cerr<<"ERROR: Unable to remove levelpack directory "<<levels->levelpackPath<<endl;
}
//Remove it from the vector (levelpack list).
for (auto it = levelpacks->item.begin(); it != levelpacks->item.end(); ++it){
if (it->first == levels->levelpackPath) {
levelpacks->item.erase(it);
break;
}
}
//Remove it from the levelpackManager.
getLevelPackManager()->removeLevelPack(levels->levelpackPath, true);
levels = NULL;
//And call changePack.
levelpacks->value=levelpacks->item.size()-1;
changePack();
refresh(imageManager, renderer);
}
}else if(name=="cmdMoveMap"){
if(selectedNumber!=NULL){
moveLevel(imageManager,renderer);
}
}else if(name=="cmdRmMap"){
if(selectedNumber!=NULL){
//Show an "are you sure" message.
if (msgBox(imageManager, renderer, tfm::format(_("Are you sure remove the map '%s'?"), levels->getLevel(selectedNumber->getNumber())->name), MsgBoxYesNo, _("Remove prompt")) != MsgBoxYes) {
return;
}
if(packPath!=CUSTOM_LEVELS_PATH){
if(!removeFile((levels->levelpackPath+"/"+levels->getLevel(selectedNumber->getNumber())->file).c_str())){
cerr<<"ERROR: Unable to remove level "<<(levels->levelpackPath+"/"+levels->getLevel(selectedNumber->getNumber())->file).c_str()<<endl;
}
levels->removeLevel(selectedNumber->getNumber());
levels->saveLevels(levels->levelpackPath+"/levels.lst");
}else{
//This is the levels levelpack so we just remove the file.
if(!removeFile(levels->getLevel(selectedNumber->getNumber())->file.c_str())){
cerr<<"ERROR: Unable to remove level "<<levels->getLevel(selectedNumber->getNumber())->file<<endl;
}
levels->removeLevel(selectedNumber->getNumber());
}
//And refresh the selection screen.
refresh(imageManager, renderer);
}
}else if(name=="cmdEdit"){
if(selectedNumber!=NULL){
levels->setCurrentLevel(selectedNumber->getNumber());
setNextState(STATE_LEVEL_EDITOR);
}
}
-
+
//Check for levelpack properties events.
- if(name=="cfgOK"){
+ else if(name=="cfgOK"){
GUIObject *lvlpackName = GUIObjectRoot->getChild("LvlpackName");
GUIObject *lvlpackDescription = GUIObjectRoot->getChild("LvlpackDescription");
GUIObject *lvlpackCongratulation = GUIObjectRoot->getChild("LvlpackCongratulation");
GUIObject *lvlpackMusic = GUIObjectRoot->getChild("LvlpackMusic");
assert(lvlpackName && lvlpackDescription && lvlpackCongratulation && lvlpackMusic);
if (lvlpackName->caption.empty()) {
msgBox(imageManager, renderer, _("Levelpack name cannot be empty."), MsgBoxOKOnly, _("Error"));
} else {
//Check if the name changed.
if (packName != lvlpackName->caption) {
std::string newPackPathMinusSlash = getUserPath(USER_DATA) + "custom/levelpacks/" + escapeFileName(lvlpackName->caption);
//Delete the old one.
if (!packName.empty()){
std::string oldPackPathMinusSlash = levels->levelpackPath;
if (!oldPackPathMinusSlash.empty()) {
if (oldPackPathMinusSlash[oldPackPathMinusSlash.size() - 1] == '/'
|| oldPackPathMinusSlash[oldPackPathMinusSlash.size() - 1] == '\\')
{
oldPackPathMinusSlash.pop_back();
}
}
if (!renameDirectory(oldPackPathMinusSlash.c_str(), newPackPathMinusSlash.c_str())) {
cerr << "ERROR: Unable to move levelpack directory '" << oldPackPathMinusSlash << "' to '"
<< newPackPathMinusSlash << "'! The levelpack directory will be kept unchanged." << endl;
//If we failed to rename the directory, we just keep the old directory name.
newPackPathMinusSlash = oldPackPathMinusSlash;
}
//Remove the old one from the levelpack manager.
getLevelPackManager()->removeLevelPack(levels->levelpackPath, false);
//And the levelpack list.
for (auto it = levelpacks->item.begin(); it != levelpacks->item.end(); ++it){
if (it->first == levels->levelpackPath) {
levelpacks->item.erase(it);
break;
}
}
} else {
//It's a new levelpack. First we try to create the dirs and the levels.lst.
if (dirExists(newPackPathMinusSlash.c_str())) {
cerr << "ERROR: The levelpack directory " << newPackPathMinusSlash << " already exists!" << endl;
msgBox(imageManager, renderer, tfm::format(_("The levelpack directory '%s' already exists!"), newPackPathMinusSlash), MsgBoxOKOnly, _("Error"));
return;
}
if (!createDirectory(newPackPathMinusSlash.c_str())) {
cerr << "ERROR: Unable to create levelpack directory " << newPackPathMinusSlash << endl;
msgBox(imageManager, renderer, tfm::format(_("Unable to create levelpack directory '%s'!"), newPackPathMinusSlash), MsgBoxOKOnly, _("Error"));
return;
}
if (fileExists((newPackPathMinusSlash + "/levels.lst").c_str())) {
cerr << "ERROR: The levelpack file " << (newPackPathMinusSlash + "/levels.lst") << " already exists!" << endl;
msgBox(imageManager, renderer, tfm::format(_("The levelpack file '%s' already exists!"), newPackPathMinusSlash + "/levels.lst"), MsgBoxOKOnly, _("Error"));
return;
}
if (!createFile((newPackPathMinusSlash + "/levels.lst").c_str())) {
cerr << "ERROR: Unable to create levelpack file " << (newPackPathMinusSlash + "/levels.lst") << endl;
msgBox(imageManager, renderer, tfm::format(_("Unable to create levelpack file '%s'!"), newPackPathMinusSlash + "/levels.lst"), MsgBoxOKOnly, _("Error"));
return;
}
//If it's successful we create a new levelpack.
levels = new LevelPack;
}
//And set the new name.
packName = levels->levelpackName = lvlpackName->caption;
packPath = levels->levelpackPath = newPackPathMinusSlash + "/";
//Also add the levelpack location
getLevelPackManager()->addLevelPack(levels);
levelpacks->addItem(packPath, packName);
levelpacks->value = levelpacks->item.size() - 1;
//And call changePack.
changePack();
}
levels->levelpackDescription = lvlpackDescription->caption;
levels->congratulationText = lvlpackCongratulation->caption;
levels->levelpackMusicList = lvlpackMusic->caption;
//Refresh the leveleditselect to show the correct information.
refresh(imageManager, renderer);
//Save the configuration.
levels->saveLevels(levels->levelpackPath + "levels.lst");
getSettings()->setValue("lastlevelpack", levels->levelpackPath);
//Clear the gui.
if (GUIObjectRoot) {
delete GUIObjectRoot;
GUIObjectRoot = NULL;
}
}
}else if(name=="cfgCancel"){
//Check if packName is empty, if so it was a new levelpack and we need to revert to an existing one.
if(packName.empty()){
packPath = levelpacks->item[levelpacks->value].first;
packName = levelpacks->item[levelpacks->value].second;
changePack();
}
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
+ } else if (name == "sllb") {
+ GUIObject *sllb = GUIObjectRoot->getChild("sllb");
+ if (sllb) {
+ for (int i = 0, m = packPropertiesFrames.size(); i < m; i++) {
+ for (auto o : packPropertiesFrames[i]) {
+ o->visible = (i == sllb->value);
+ }
+ }
+ }
+ } else if (name == "cfgExportPOT") {
+ if (LevelPackPOTExporter::exportPOT(levels->levelpackPath)) {
+ msgBox(imageManager, renderer,
+ tfm::format(_("The translation template is exported at\n'%s'."), levels->levelpackPath + "locale/messages.pot"),
+ MsgBoxOKOnly,
+ _("Export translation template"));
+ } else {
+ msgBox(imageManager, renderer,
+ _("Failed to export translation template."),
+ MsgBoxOKOnly,
+ _("Error"));
+ }
}
-
//Check for add level events.
- if(name=="cfgAddOK"){
+ else if(name=="cfgAddOK"){
//Check if the file name isn't null.
//Now loop throught the children of the GUIObjectRoot in search of the fields.
for(unsigned int i=0;i<GUIObjectRoot->childControls.size();i++){
if(GUIObjectRoot->childControls[i]->name=="LvlFile"){
if(GUIObjectRoot->childControls[i]->caption.empty()){
msgBox(imageManager,renderer,_("No file name given for the new level."),MsgBoxOKOnly,_("Missing file name"));
return;
}else{
string tmp_caption = GUIObjectRoot->childControls[i]->caption;
//Replace all spaces with a underline.
size_t j;
for(;(j=tmp_caption.find(" "))!=string::npos;){
tmp_caption.replace(j,1,"_");
}
//If there isn't ".map" extension add it.
size_t found=tmp_caption.find_first_of(".");
if(found!=string::npos)
tmp_caption.replace(tmp_caption.begin()+found+1,tmp_caption.end(),"map");
else if (tmp_caption.substr(found+1)!="map")
tmp_caption.append(".map");
/* Create path and file in it */
string path=(levels->levelpackPath+"/"+tmp_caption);
if(packPath==CUSTOM_LEVELS_PATH){
path=(getUserPath(USER_DATA)+"/custom/levels/"+tmp_caption);
}
//First check if the file doesn't exist already.
FILE* f;
f=fopen(path.c_str(),"rb");
//Check if it exists.
if(f){
//Close the file.
fclose(f);
//Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
currentState->render(imageManager,renderer);
levelEditGUIObjectRoot->render(renderer);
//Notify the user.
msgBox(imageManager, renderer, tfm::format(_("The file %s already exists."), tmp_caption), MsgBoxOKOnly, _("Error"));
return;
}
if(!createFile(path.c_str())){
cerr<<"ERROR: Unable to create level file "<<path<<endl;
}else{
//Update statistics.
statsMgr.newAchievement("create1");
if((++statsMgr.createdLevels)>=10) statsMgr.newAchievement("create10");
}
levels->addLevel(path);
//NOTE: Also add the level to the levels levelpack in case of custom levels.
if(packPath==CUSTOM_LEVELS_PATH){
LevelPack* levelsPack=getLevelPackManager()->getLevelPack(LEVELS_PATH);
if(levelsPack){
levelsPack->addLevel(path);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
}else{
cerr<<"ERROR: Unable to add level to Levels levelpack"<<endl;
}
}
if(packPath!=CUSTOM_LEVELS_PATH)
levels->saveLevels(levels->levelpackPath+"levels.lst");
refresh(imageManager, renderer);
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
return;
}
}
}
}
}else if(name=="cfgAddCancel"){
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
-
+
//Check for move level events.
- if(name=="cfgMoveOK"){
+ else if(name=="cfgMoveOK"){
//Check if the entered level number is valid.
//Now loop throught the children of the GUIObjectRoot in search of the fields.
int level=0;
int placement=0;
for(unsigned int i=0;i<GUIObjectRoot->childControls.size();i++){
if(GUIObjectRoot->childControls[i]->name=="MoveLevel"){
level=atoi(GUIObjectRoot->childControls[i]->caption.c_str());
if(level<=0 || level>levels->getLevelCount()){
msgBox(imageManager,renderer,_("The entered level number isn't valid!"),MsgBoxOKOnly,_("Illegal number"));
return;
}
}
if(GUIObjectRoot->childControls[i]->name=="lstPlacement"){
placement=GUIObjectRoot->childControls[i]->value;
}
}
//Now we execute the swap/move.
//Check for the place before.
if(placement==0){
//We place the selected level before the entered level.
levels->moveLevel(selectedNumber->getNumber(),level-1);
}else if(placement==1){
//We place the selected level after the entered level.
if(level<selectedNumber->getNumber())
levels->moveLevel(selectedNumber->getNumber(),level);
else
levels->moveLevel(selectedNumber->getNumber(),level+1);
}else if(placement==2){
//We swap the selected level with the entered level.
levels->swapLevel(selectedNumber->getNumber(),level-1);
}
//And save the change.
if(packPath!=CUSTOM_LEVELS_PATH)
levels->saveLevels(levels->levelpackPath+"/levels.lst");
refresh(imageManager, renderer);
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}else if(name=="cfgMoveCancel"){
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
}
diff --git a/src/LevelEditSelect.h b/src/LevelEditSelect.h
index de4879d..dc6b30d 100644
--- a/src/LevelEditSelect.h
+++ b/src/LevelEditSelect.h
@@ -1,99 +1,102 @@
/*
* Copyright (C) 2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LEVELEDITSELECT_H
#define LEVELEDITSELECT_H
#include "LevelSelect.h"
#include "GameState.h"
#include "GUIObject.h"
#include <vector>
#include <string>
//This is the LevelEditSelect state, here you can select levelpacks and levels.
class LevelEditSelect :public LevelSelect{
private:
//Pointer to the GUIObjectRoot of the levelselect main gui.
GUIObject* levelEditGUIObjectRoot;
//Pointer to the new levelpack textfield.
GUIObject* levelpackName;
//Pointer to the new levelpack button.
GUIButton* newPack;
//Pointer to the levelpack properties button.
GUIButton* propertiesPack;
//Pointer to the remove levelpack button.
GUIButton* removePack;
//Pointer to the move map button.
GUIButton* move;
//Pointer to the remove map button.
GUIButton* remove;
//Pointer to the edit map button.
GUIButton* edit;
//If is vertical mode, then the buttons are 2*3 instead of 3*2
bool isVertical;
//String that contains the name/path of the current levelpack.
std::string packName, packPath;
+
+ //GUIObjects of each frame in pack properties dialog.
+ std::vector<std::vector<GUIObject*> > packPropertiesFrames;
//Method that will create the GUI elements.
//initial: Boolean if it is the first time the gui is created.
void createGUI(ImageManager& imageManager, SDL_Renderer& renderer, bool initial);
//Method that should be called when changing the current levelpack in an abnormal way.
void changePack();
//This method will show a popup with levelpack specific settings.
//newPack: Boolean if it's a new levelpack.
void packProperties(ImageManager& imageManager, SDL_Renderer &renderer, bool newPack);
//This method will show an add level dialog.
void addLevel(ImageManager& imageManager, SDL_Renderer &renderer);
//This method will show an move level dialog.
void moveLevel(ImageManager& imageManager, SDL_Renderer &renderer);
public:
//Constructor.
LevelEditSelect(ImageManager &imageManager, SDL_Renderer& renderer);
//Destructor.
~LevelEditSelect();
//Inherited from LevelSelect.
//change: Boolean if the levelpack changed, if not we only have to rearrange the numbers.
void refresh(ImageManager &imageManager, SDL_Renderer &renderer, bool change=true) override;
void selectNumber(ImageManager &imageManager, SDL_Renderer &renderer, unsigned int number,bool selected) override;
void handleEvents(ImageManager& imageManager, SDL_Renderer& renderer) override;
//Inherited from GameState.
void render(ImageManager&imageManager, SDL_Renderer& renderer) override;
//Inherited from GameState.
void resize(ImageManager &imageManager, SDL_Renderer& renderer) override;
//Inherited from LevelSelect.
void renderTooltip(SDL_Renderer& renderer,unsigned int number,int dy) override;
//GUI events will be handled here.
void GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType) override;
};
#endif
diff --git a/src/LevelPackPOTExporter.cpp b/src/LevelPackPOTExporter.cpp
new file mode 100644
index 0000000..c674c15
--- /dev/null
+++ b/src/LevelPackPOTExporter.cpp
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2019 Me and My Shadow
+ *
+ * This file is part of Me and My Shadow.
+ *
+ * Me and My Shadow is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Me and My Shadow is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "LevelPackPOTExporter.h"
+#include "Functions.h"
+#include "FileManager.h"
+#include "POASerializer.h"
+#include "TreeStorageNode.h"
+#include <string.h>
+#include <time.h>
+#include <stdio.h>
+#include <string>
+#include <vector>
+#include <map>
+#include <fstream>
+#include <algorithm>
+#include <iostream>
+
+struct POTFileEntry {
+ std::string msgid, msgid_plural;
+ std::string comments, sources;
+};
+
+//Check if a string contains c-format specifier.
+//Currently we only support %[+-]?[0-9]*[.]?[0-9]*[diufFeEgGaAxXoscp]
+static bool isCFormat(const std::string& s) {
+ for (int i = 0, m = s.size(); i < m; i++) {
+ if (s[i] == '%') {
+ i++;
+ if (i < m) {
+ switch (s[i]) {
+ case '+': case '-':
+ i++;
+ break;
+ }
+ }
+ int dotCount = 0;
+ while (i < m) {
+ switch (s[i]) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ break;
+ case '.':
+ dotCount++;
+ break;
+ default:
+ dotCount = 2;
+ break;
+ }
+ if (dotCount >= 2) break;
+ i++;
+ }
+ if (i < m) {
+ switch (s[i]) {
+ case 'd': case 'i': case 'u': case 'f': case 'F':
+ case 'e': case 'E': case 'g': case 'G': case 'a': case 'A':
+ case 'x': case 'X': case 'o': case 's': case 'c': case 'p':
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+static void writeComment(std::ostream& fout, const std::string& comments, const std::string& prefix) {
+ std::string message;
+
+ for (auto c : comments) {
+ if (c != '\r') message.push_back(c);
+ }
+
+ //Trim the message.
+ {
+ size_t lps = message.find_first_not_of('\n'), lpe = message.find_last_not_of("\n \t");
+ if (lps == std::string::npos || lpe == std::string::npos || lps > lpe) {
+ message.clear(); // it's completely empty
+ } else {
+ message = message.substr(lps, lpe - lps + 1);
+ }
+ }
+
+ if (!message.empty()) {
+ message.push_back('\0');
+
+ //Split the message into lines.
+ for (int lps = 0;;) {
+ // determine the end of line
+ int lpe = lps;
+ for (; message[lpe] != '\n' && message[lpe] != '\0'; lpe++);
+
+ // output the line
+ fout << prefix << message.substr(lps, lpe - lps) << std::endl;
+
+ // break if the string ends
+ if (message[lpe] == '\0') break;
+
+ // point to the start of next line
+ lps = lpe + 1;
+ }
+ }
+}
+
+static void writeHeader(std::ostream& fout) {
+ time_t rawtime;
+ struct tm *timeinfo;
+ char buffer[256];
+
+ time(&rawtime);
+ timeinfo = gmtime(&rawtime);
+
+ //FIXME: I can't make %z print timezone under Windows
+
+ strftime(buffer, sizeof(buffer), "\"POT-Creation-Date: %Y-%m-%d %H:%M+0000\\n\"", timeinfo);
+
+ fout << "# SOME DESCRIPTIVE TITLE." << std::endl;
+ fout << "# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER" << std::endl;
+ fout << "# This file is distributed under the same license as the PACKAGE package." << std::endl;
+ fout << "# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR." << std::endl;
+ fout << "#" << std::endl;
+ fout << "#, fuzzy" << std::endl;
+ fout << "msgid \"\"" << std::endl;
+ fout << "msgstr \"\"" << std::endl;
+ fout << "\"Project-Id-Version: PACKAGE VERSION\\n\"" << std::endl;
+ fout << "\"Report-Msgid-Bugs-To: \\n\"" << std::endl;
+ fout << buffer << std::endl;
+ fout << "\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"" << std::endl;
+ fout << "\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"" << std::endl;
+ fout << "\"Language-Team: LANGUAGE <LL@li.org>\\n\"" << std::endl;
+ fout << "\"Language: \\n\"" << std::endl;
+ fout << "\"MIME-Version: 1.0\\n\"" << std::endl;
+ fout << "\"Content-Type: text/plain; charset=UTF-8\\n\"" << std::endl;
+ fout << "\"Content-Transfer-Encoding: 8bit\\n\"" << std::endl;
+ fout << "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"" << std::endl;
+ fout << std::endl;
+}
+
+class POTFileEntries {
+public:
+ std::vector<POTFileEntry> entries;
+ std::map<std::string, int> lookupTable;
+public:
+ //Add a msgid to the entries.
+ void addEntry(const std::string& msgid, const std::string& msgid_plural, const std::string& comments, const std::string& sources) {
+ if (msgid.empty()) return;
+
+ int index;
+ {
+ std::string key = msgid + "\x01" + msgid_plural;
+ auto it = lookupTable.find(key);
+ if (it == lookupTable.end()) {
+ entries.emplace_back();
+ lookupTable[key] = index = entries.size() - 1;
+ entries[index].msgid = msgid;
+ entries[index].msgid_plural = msgid_plural;
+ } else {
+ index = it->second;
+ }
+ }
+
+ if (!comments.empty()) {
+ if (comments.back() == '\n') {
+ entries[index].comments += comments;
+ } else {
+ entries[index].comments += comments + "\n";
+ }
+ }
+
+ if (!sources.empty()) {
+ if (sources.back() == '\n') {
+ entries[index].sources += sources;
+ } else {
+ entries[index].sources += sources + "\n";
+ }
+ }
+ }
+
+ //Write all the entries to file.
+ void writeEntry(std::ostream& fout) {
+ for (auto &entry : entries) {
+ //Write comments.
+ writeComment(fout, entry.comments, "# ");
+
+ //Write sources.
+ writeComment(fout, entry.sources, "#: ");
+
+ //Check if it's c-format.
+ if (isCFormat(entry.msgid) || isCFormat(entry.msgid_plural)) {
+ fout << "#, c-format" << std::endl;
+ }
+
+ //Write msgids.
+ fout << "msgid \"" << escapeCString(entry.msgid) << "\"" << std::endl;
+ if (entry.msgid_plural.empty()) {
+ fout << "msgstr \"\"" << std::endl;
+ } else {
+ fout << "msgid_plural \"" << escapeCString(entry.msgid_plural) << "\"" << std::endl;
+ fout << "msgstr[0] \"\"" << std::endl;
+ fout << "msgstr[1] \"\"" << std::endl;
+ }
+
+ //Add a newline.
+ fout << std::endl;
+ }
+ }
+};
+
+static std::string formatSource(const std::string& fileName, const ITreeStorageBuilder::FilePosition& pos) {
+ char s[32];
+ sprintf(s, "%d", pos.row);
+ return fileName + ":" + s;
+}
+
+class LoadLevelListTreeStorageNode : public TreeStorageNode {
+public:
+ POTFileEntries *pot;
+public:
+ LoadLevelListTreeStorageNode(POTFileEntries *pot) : TreeStorageNode(), pot(pot) {}
+ virtual bool newAttribute(const std::string& name, const std::vector<std::string>& value, const FilePosition& namePos, const std::vector<FilePosition>& valuePos) override {
+ //Do our own stuff first.
+ if (name == "name" && value.size() >= 1) {
+ pot->addEntry(value[0], "",
+ "TRANSLATORS: This is the name of the level pack.",
+ formatSource("levels.lst", valuePos[0]));
+ } else if (name == "description" && value.size() >= 1) {
+ pot->addEntry(value[0], "",
+ "TRANSLATORS: This is the description of the level pack.",
+ formatSource("levels.lst", valuePos[0]));
+ } else if (name == "congratulations" && value.size() >= 1) {
+ pot->addEntry(value[0], "",
+ "TRANSLATORS: This will be shown when all the levels in the pack are finished.",
+ formatSource("levels.lst", valuePos[0]));
+ }
+
+ //Do default stuff.
+ return TreeStorageNode::newAttribute(name, value, namePos, valuePos);
+ }
+};
+
+class LoadLevelMessageTreeStorageNode : public TreeStorageNode {
+public:
+ POTFileEntries *pot;
+ std::string fileName;
+public:
+ LoadLevelMessageTreeStorageNode(POTFileEntries *pot, const std::string& fileName) : TreeStorageNode(), pot(pot), fileName(fileName) {}
+ virtual ITreeStorageBuilder* newNode() override {
+ return new LoadLevelMessageTreeStorageNode(pot, fileName);
+ }
+ virtual bool newAttribute(const std::string& name, const std::vector<std::string>& value, const FilePosition& namePos, const std::vector<FilePosition>& valuePos) override {
+ //Do our own stuff first.
+ if (name == "name" && value.size() >= 1) {
+ pot->addEntry(value[0], "",
+ "TRANSLATORS: This is the name of a level.",
+ formatSource(fileName, valuePos[0]));
+ } else if (name == "message" && value.size() >= 1) {
+ if (this->name == "tile" && this->value.size() >= 1 && this->value[0] == "NotificationBlock") {
+ pot->addEntry(unescapeNewline(value[0]), "", "",
+ formatSource(fileName, valuePos[0]));
+ } else {
+ pot->addEntry(value[0], "", "",
+ formatSource(fileName, valuePos[0]));
+ }
+ }
+
+ //TODO: extract strings from script.
+
+ //Do default stuff.
+ return TreeStorageNode::newAttribute(name, value, namePos, valuePos);
+ }
+};
+
+bool LevelPackPOTExporter::exportPOT(const std::string& levelpackPath) {
+ //Open the level list.
+ std::string levelListFile = levelpackPath + "levels.lst";
+ std::ifstream fin(levelListFile.c_str());
+ if (!fin) {
+ std::cerr << "ERROR: Can't load level list " << levelListFile << std::endl;
+ return false;
+ }
+
+ //Create the entries.
+ POTFileEntries pot;
+
+ //Load the level list file.
+ LoadLevelListTreeStorageNode obj(&pot);
+ if (!POASerializer().readNode(fin, &obj, true)){
+ std::cerr << "ERROR: Invalid file format of level list " << levelListFile << std::endl;
+ return false;
+ }
+
+ //Loop through the level list entries.
+ for (unsigned int i = 0; i<obj.subNodes.size(); i++){
+ TreeStorageNode* obj1 = obj.subNodes[i];
+ if (obj1 == NULL)
+ continue;
+ if (!obj1->value.empty() && obj1->name == "levelfile") {
+ std::string fileName = obj1->value[0];
+
+ //The path to the file to open.
+ std::string levelFile = levelpackPath + fileName;
+
+ //Open the level file.
+ LoadLevelMessageTreeStorageNode obj(&pot, fileName);
+ if (!POASerializer().loadNodeFromFile(levelFile.c_str(), &obj, true)) {
+ return false;
+ }
+ }
+ }
+
+ //Create the directory.
+ createDirectory((levelpackPath + "locale").c_str());
+
+ //Create the messages.pot
+ std::string potFile = levelpackPath + "locale/messages.pot";
+ std::ofstream fout(potFile.c_str());
+ if (!fout) {
+ std::cerr << "ERROR: Can't open the file " << potFile << " for save" << std::endl;
+ return false;
+ }
+
+ //Write entries.
+ writeHeader(fout);
+ pot.writeEntry(fout);
+
+ //Over.
+ return true;
+}
diff --git a/src/LevelPackPOTExporter.h b/src/LevelPackPOTExporter.h
new file mode 100644
index 0000000..b4002bd
--- /dev/null
+++ b/src/LevelPackPOTExporter.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 Me and My Shadow
+ *
+ * This file is part of Me and My Shadow.
+ *
+ * Me and My Shadow is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Me and My Shadow is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LEVELPACKPOTEXPORTER_H
+#define LEVELPACKPOTEXPORTER_H
+
+#include <string>
+
+class LevelPackPOTExporter {
+public:
+ //Export the messages.pot of a level pack.
+ //levelpackPath: The path to a level pack. Must ends with '/' or '\'.
+ //Return value: true if export succeded.
+ static bool exportPOT(const std::string& levelpackPath);
+};
+
+#endif
+
diff --git a/src/POASerializer.cpp b/src/POASerializer.cpp
index 0f19f34..7b82b9f 100644
--- a/src/POASerializer.cpp
+++ b/src/POASerializer.cpp
@@ -1,504 +1,551 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "POASerializer.h"
#include <sstream>
using namespace std;
-static void readString(std::istream& fin,std::string& string){
- //This method is used for reading a string from an input stream.
- //fin: The input stream to read from.
- //string: String to place the result in.
-
+//This method is used for reading a string from an input stream.
+//fin: The input stream to read from.
+//string: String to place the result in.
+static void readString(std::istream& fin, std::string& string, ITreeStorageBuilder::FilePosition& pos) {
//The current character.
int c;
- c=fin.get();
-
+ c = fin.get();
+ ITreeStorageBuilder::FilePosition lastPos = pos; pos.advanceByCharacter(c);
+
//Check if there's a '"'.
if(c=='\"'){
//There's a '"' so place every character we encounter in the string without parsing.
while((!fin.eof()) & (!fin.fail())){
//First we get the next character to prevent putting the '"' in the string.
c=fin.get();
-
+ lastPos = pos; pos.advanceByCharacter(c);
+
//Check if there's a '"' since that could mean the end of the string.
if(c=='\"'){
//Get the next character and check if that's also an '"'.
c=fin.get();
- if(c!='\"'){
+ lastPos = pos; pos.advanceByCharacter(c);
+ if (c != '\"') {
//We have two '"' after each other meaning an escaped '"'.
//We unget one so there will be one '"' placed in the string.
fin.unget();
+ pos = lastPos;
return;
}
}
-
+
//Every other character can be put in the string.
string.push_back(c);
}
}else{
//There are no quotes around the string so we need to be carefull detecting if the string has ended.
do{
switch(c){
//Check for characters that mean the end of the string.
case EOF:
case ' ':
case '\r':
case '\n':
case '\t':
return;
//Check for characters that are part of the POA file format.
//If so we first unget one character to prevent problems parsing the rest of the file.
case ',':
case '=':
case '(':
case ')':
case '{':
case '}':
case '#':
fin.unget();
+ pos = lastPos;
return;
default:
//In any other case the character is normal so we put it in the string.
string.push_back(c);
}
-
+
//Get the next character.
c=fin.get();
+ lastPos = pos; pos.advanceByCharacter(c);
}while((!fin.eof()) & (!fin.fail()));
}
}
-static void skipWhitespaces(std::istream& fin){
- //This function will read from the input stream until there's something else than whitespaces.
- //fin: The input stream to read from.
-
+//This function will read from the input stream until there's something else than whitespaces.
+//fin: The input stream to read from.
+static void skipWhitespaces(std::istream& fin, ITreeStorageBuilder::FilePosition& pos) {
//The current character.
int c;
+ ITreeStorageBuilder::FilePosition lastPos;
while((!fin.eof()) & (!fin.fail())){
//Get the character.
c=fin.get();
-
+ lastPos = pos; pos.advanceByCharacter(c);
+
//Check if it's one of the whitespace characters.
switch(c){
case EOF:
case ' ':
case '\r':
case '\n':
case '\t':
break;
default:
//Anything other means that the whitespaces have ended.
//Unget the last character and return.
fin.unget();
+ pos = lastPos;
return;
}
}
}
-static void skipComment(std::istream& fin){
- //This function will read from the input stream until the end of a line (also end of the comment).
- //fin: The input stream to read from.
-
+//This function will read from the input stream until the end of a line (also end of the comment).
+//fin: The input stream to read from.
+static void skipComment(std::istream& fin, ITreeStorageBuilder::FilePosition& pos) {
//The current character.
int c;
- while((!fin.eof()) & (!fin.fail())){
+ ITreeStorageBuilder::FilePosition lastPos;
+ while ((!fin.eof()) & (!fin.fail())){
//Get the character.
c=fin.get();
-
+ lastPos = pos; pos.advanceByCharacter(c);
+
//Check if it's a new line (end of comment).
if(c=='\r'||c=='\n'){
fin.unget();
+ pos = lastPos;
break;
}
}
}
bool POASerializer::readNode(std::istream& fin,ITreeStorageBuilder* objOut,bool loadSubNodeOnly){
+ //The current file position.
+ ITreeStorageBuilder::FilePosition pos = { 1, 1 }, lastPos, tempPos;
//The current character.
int c;
//The current mode of reading.
enum ReadMode {
ReadName = 0,
ReadAttributeValue = 1,
ReadSubnodeValue = 2,
AddFirst = 16,
AddAttribute = 16,
AddSubnode = 17,
} mode = ReadName;
//Before reading make sure that the input stream isn't null.
if(!fin) return false;
-
+
//Vector containing the stack of TreeStorageNodes.
vector<ITreeStorageBuilder*> stack;
//A vector for the names and a vector for the values.
vector<string> names,values;
+ //Positions of the names and values.
+ vector<ITreeStorageBuilder::FilePosition> namePos, valuePos;
+
//Check if we only need to load subNodes.
//If so then put the objOut as the first TreeStorageNode.
if(loadSubNodeOnly) stack.push_back(objOut);
//Loop through the files.
while((!fin.eof()) && (!fin.fail())){
//Get a character.
c=fin.get();
-
+ lastPos = pos; pos.advanceByCharacter(c);
+
//Check what it is and what to do with that character.
switch(c){
case EOF:
case ' ':
case '\r':
case '\n':
case '\t':
//We skip whitespaces.
break;
case '#':
//A comment so skip it.
- skipComment(fin);
+ skipComment(fin, pos);
break;
case '}':
//A closing bracket so do one step back in the stack.
//There must be a TreeStorageNode left if not return false.
if(stack.empty()) return false;
-
+
//Remove the last entry of the stack.
stack.pop_back();
//Check if the stack is empty, if so than the reading of the node is done.
if(stack.empty()) return true;
objOut=stack.back();
break;
default:
//It isn't a special character but part of a name/value, so unget it.
fin.unget();
-
+ pos = lastPos;
+
{
//Clear the names and values vectors, start reading new names/values.
names.clear();
values.clear();
-
+ namePos.clear();
+ valuePos.clear();
+
//Set the mode to the read name mode.
mode=ReadName;
-
+
//Keep reading characters, until we break out the while loop or there's an error.
while((!fin.eof()) & (!fin.fail())){
//The string containing the name.
string s;
-
+
//First skip the whiteSpaces.
- skipWhitespaces(fin);
+ skipWhitespaces(fin,pos);
//Now get the string.
- readString(fin,s);
-
+ tempPos = pos;
+ readString(fin,s,pos);
+
//Check the mode.
switch(mode){
case ReadName:
//Mode is 0(read names) so put the string in the names vector.
names.push_back(s);
+ namePos.push_back(tempPos);
break;
case ReadAttributeValue:
case ReadSubnodeValue:
//Mode is 1 or 2 so put the string in the values vector.
values.push_back(s);
+ valuePos.push_back(tempPos);
break;
}
//Again skip whitespaces.
- skipWhitespaces(fin);
-
+ skipWhitespaces(fin,pos);
+
//Now read the next character.
c=fin.get();
- switch(c){
+ lastPos = pos; pos.advanceByCharacter(c);
+ switch (c) {
case ',':
//A comma means one more name or value.
break;
case '=':
//An '=' can only occur after a name (mode=0).
if(mode==ReadName){
//The next string will be a value so set mode to 1.
mode=ReadAttributeValue;
}else{
//In any other case there's something wrong so return false.
return false;
}
break;
case '(':
//An '(' can only occur after a name (mode=0).
if(mode==ReadName){
//The next string will be a value of a block so set mode to 2.
mode=ReadSubnodeValue;
}else{
//In any other case there's something wrong so return false.
return false;
}
break;
case ')':
//A ')' can only occur after an attribute (mode=2).
if(mode==ReadSubnodeValue){
//The next will be a new subNode so set mode to 17.
mode=AddSubnode;
}else{
//In any other case there's something wrong so return false.
return false;
}
break;
case '{':
//A '{' can only mean a new subNode (mode=17).
fin.unget();
+ pos = lastPos;
mode=AddSubnode;
break;
default:
//The character is not special so unget it.
fin.unget();
+ pos = lastPos;
mode=AddAttribute;
break;
}
-
+
//We only need to break out if the mode is 16(add attribute) or 17(add subnode)
if(mode>=AddFirst) break;
}
-
+
//Check the mode.
switch(mode){
case AddAttribute:
//The mode is 16 so we need to change the names and values into attributes.
//The stack mustn't be empty.
if(stack.empty()) return false;
-
+
//Make sure that the result TreeStorageNode isn't null.
if(objOut!=NULL){
//Check if the names vector is empty, if so add an empty name.
- if(names.empty()) names.push_back("");
-
+ if (names.empty()) {
+ names.push_back("");
+ namePos.push_back(pos);
+ }
+
//Put an empty value for every valueless name.
- while(values.size()<names.size()) values.push_back("");
-
+ while (values.size() < names.size()) {
+ values.push_back("");
+ valuePos.push_back(pos);
+ }
+
//Now loop through the names.
for(unsigned int i=0;i<names.size()-1;i++){
//Temp vector that will contain the values.
vector<string> v;
v.push_back(values[i]);
-
+
+ //Temp vector that will contain the positions of values.
+ vector<ITreeStorageBuilder::FilePosition> vPos;
+ vPos.push_back(valuePos[i]);
+
//And add the attribute.
- if (objOut->newAttribute(names[i], v)) {
+ if (objOut->newAttribute(names[i], v, namePos[i], vPos)) {
//Early exit.
return true;
}
}
-
- if(names.size()>1) values.erase(values.begin(),values.begin()+(names.size()-1));
- if (objOut->newAttribute(names.back(), values)) {
+
+ if (names.size() > 1) {
+ values.erase(values.begin(), values.begin() + (names.size() - 1));
+ valuePos.erase(valuePos.begin(), valuePos.begin() + (names.size() - 1));
+ }
+ if (objOut->newAttribute(names.back(), values, namePos.back(), valuePos)) {
//Early exit.
return true;
}
}
break;
case AddSubnode:
//The mode is 17 so we need to add a subNode.
{
//Check if the names vector is empty, if so add an empty name.
- if(names.empty()) names.push_back("");
- else if(names.size()>1){
+ if (names.empty()) {
+ names.push_back("");
+ namePos.push_back(pos);
+ } else if (names.size() > 1){
if(stack.empty()) return false;
- while(values.size()<names.size()) values.push_back("");
+ while (values.size() < names.size()) {
+ values.push_back("");
+ valuePos.push_back(pos);
+ }
for(unsigned int i=0;i<names.size()-1;i++){
vector<string> v;
v.push_back(values[i]);
- if (objOut->newAttribute(names[i], v)) {
+ vector<ITreeStorageBuilder::FilePosition> vPos;
+ vPos.push_back(valuePos[i]);
+ if (objOut->newAttribute(names[i], v, namePos[i], vPos)) {
//Early exit.
return true;
}
}
- values.erase(values.begin(),values.begin()+(names.size()-1));
+ values.erase(values.begin(), values.begin() + (names.size() - 1));
+ valuePos.erase(valuePos.begin(), valuePos.begin() + (names.size() - 1));
}
-
+
//Create a new subNode.
ITreeStorageBuilder* objNew=NULL;
-
+
//If the stack is empty the new subNode will be the result TreeStorageNode.
if(stack.empty()) objNew=objOut;
//If not the new subNode will be a subNode of the result TreeStorageNode.
else if (objOut != NULL) {
objNew = objOut->newNode();
if (objNew == NULL) {
//Early exit.
return true;
}
}
-
+
//Add it to the stack.
stack.push_back(objNew);
if(objNew!=NULL){
//Add the name and the values.
- if (objNew->setName(names.back()) || objNew->setValue(values)) {
+ if (objNew->setName(names.back(), namePos.back()) || objNew->setValue(values, valuePos)) {
//Early exit.
return true;
}
}
objOut=objNew;
//Skip the whitespaces.
- skipWhitespaces(fin);
+ skipWhitespaces(fin,pos);
//And get the next character.
c=fin.get();
+ lastPos = pos; pos.advanceByCharacter(c);
if(c!='{'){
//The character isn't a '{' meaning the block hasn't got a body.
fin.unget();
+ pos = lastPos;
stack.pop_back();
-
+
//Check if perhaps we're done, stack=empty.
if(stack.empty()) return true;
objOut=stack.back();
}
}
break;
default:
//The mode isn't 16 or 17 but still broke out the while loop.
//Something's wrong so return false.
return false;
}
}
break;
}
}
return true;
}
static void writeString(std::ostream& fout,std::string& s){
//This method will write a string.
//fout: The output stream to write to.
//s: The string to write.
-
+
//new: check if the string is empty
if(s.empty()){
//because of the new changes of loader, we should output 2 quotes '""'
fout<<"\"\"";
}else
//Check if the string contains any special character that needs escaping.
if(s.find_first_of(" \r\n\t,=(){}#\"")!=string::npos){
//It does so we put '"' around them.
fout<<'\"';
-
+
//The current character.
int c;
-
+
//Loop through the characters.
for(unsigned int i=0;i<s.size();i++){
c=s[i];
-
+
//If there's a '"' character it needs to be counter escaped. ("")
if(c=='\"'){
fout<<"\"\"";
}else{
//If it isn't we can just write away the character.
fout<<(char)c;
}
}
fout<<'\"';
}else{
//It doesn't contain any special characters so we can write it away.
fout<<s;
}
}
static void writeStringArray(std::ostream& fout,std::vector<std::string>& s){
//This method will write a away an array of strings.
//fout: The output stream to write to.
//s: Vector containing the strings to write.
-
+
//Loop the strings.
for(unsigned int i=0;i<s.size();i++){
//If it's the second or more there must be a ",".
if(i>0) fout<<',';
//Now write the string.
writeString(fout,s[i]);
}
}
static void pWriteNode(ITreeStorageReader* obj,std::ostream& fout,int indent,bool saveSubNodeOnly){
//Write the TreeStorageNode to the given output stream.
//obj: The TreeStorageNode to write away.
//fout: The output stream to write to.
//indent: Integer containing the number of indentations are needed.
//saveSubNodeOnly: Boolean if only the subNodes need to be saved.
-
+
//Boolean if the node has subNodes.
bool haveSubNodes=false;
void* lpUserData=NULL;
ITreeStorageReader* objSubNode=NULL;
string s;
vector<string> v;
//---
if(obj==NULL) return;
//---
if(!saveSubNodeOnly){
for(int i=0;i<indent;i++) fout<<'\t';
s.clear();
obj->getName(s);
writeString(fout,s);
fout<<'(';
v.clear();
obj->getValue(v);
writeStringArray(fout,v);
fout<<')';
indent++;
}
//attributes
lpUserData=NULL;
for(;;){
s.clear();
v.clear();
lpUserData=obj->getNextAttribute(lpUserData,s,v);
if(lpUserData==NULL) break;
if(!haveSubNodes && !saveSubNodeOnly) fout<<"{\n";
haveSubNodes=true;
for(int i=0;i<indent;i++) fout<<'\t';
writeString(fout,s);
fout<<'=';
writeStringArray(fout,v);
fout<<'\n';
}
//subnodes
lpUserData=NULL;
for(;;){
lpUserData=obj->getNextNode(lpUserData,objSubNode);
if(lpUserData==NULL) break;
if(objSubNode!=NULL){
if(!haveSubNodes && !saveSubNodeOnly) fout<<"{\n";
haveSubNodes=true;
pWriteNode(objSubNode,fout,indent,false);
}
}
//---
if(!saveSubNodeOnly){
indent--;
if(haveSubNodes){
for(int i=0;i<indent;i++) fout<<'\t';
fout<<'}';
}
fout<<'\n';
}
}
void POASerializer::writeNode(ITreeStorageReader* obj,std::ostream& fout,bool writeHeader,bool saveSubNodeOnly){
//Make sure that the output stream isn't null.
if(!fout) return;
-
+
//It isn't so start writing the node.
pWriteNode(obj,fout,0,saveSubNodeOnly);
}
diff --git a/src/TreeStorageNode.cpp b/src/TreeStorageNode.cpp
index e220978..ad8fd04 100644
--- a/src/TreeStorageNode.cpp
+++ b/src/TreeStorageNode.cpp
@@ -1,159 +1,159 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WIN32
#include <stdint.h>
#include <unistd.h>
#endif
#include "TreeStorageNode.h"
#include "MD5.h"
#include <string.h>
using namespace std;
TreeStorageNode::~TreeStorageNode(){
//The deconstructor will just calls destroy().
destroy();
}
void TreeStorageNode::destroy(){
//Loop through the subnodes and delete them.
for(unsigned int i=0;i<subNodes.size();i++){
delete subNodes[i];
}
//Now clear some stuff.
name.clear();
value.clear();
attributes.clear();
subNodes.clear();
}
-bool TreeStorageNode::setName(std::string& name){
+bool TreeStorageNode::setName(const std::string& name, const FilePosition& pos){
this->name=name;
return false;
}
void TreeStorageNode::getName(std::string& name){
name=this->name;
}
-bool TreeStorageNode::setValue(std::vector<std::string>& value){
+bool TreeStorageNode::setValue(const std::vector<std::string>& value, const std::vector<FilePosition>& pos){
this->value=value;
return false;
}
void TreeStorageNode::getValue(std::vector<std::string>& value){
value=this->value;
}
ITreeStorageBuilder* TreeStorageNode::newNode(){
TreeStorageNode* obj=new TreeStorageNode;
subNodes.push_back(obj);
return obj;
}
-bool TreeStorageNode::newAttribute(std::string& name,std::vector<std::string>& value){
+bool TreeStorageNode::newAttribute(const std::string& name, const std::vector<std::string>& value, const FilePosition& namePos, const std::vector<FilePosition>& valuePos){
//Put the attribute in the attributes map.
attributes[name]=value;
return false;
}
void* TreeStorageNode::getNextAttribute(void* pUserData,std::string& name,std::vector<std::string>& value){
if(pUserData==NULL) objAttrIterator=attributes.begin();
if(objAttrIterator!=attributes.end()){
name=objAttrIterator->first;
value=objAttrIterator->second;
++objAttrIterator;
return &objAttrIterator;
}else{
return NULL;
}
}
void* TreeStorageNode::getNextNode(void* pUserData,ITreeStorageReader*& obj){
intptr_t i=(intptr_t)pUserData;
//Check if the pointer is in range of the subNodes vector.
if(i<(int)subNodes.size()){
obj=subNodes[i];
return (void*)(i+1);
}else{
return NULL;
}
}
static void md5AppendString(Md5& md5,const string& s){
unsigned int sz=s.size();
unsigned char c[4];
c[0]=sz;
c[1]=sz>>8;
c[2]=sz>>16;
c[3]=sz>>24;
md5.update(c,4);
if(sz>0) md5.update(s.c_str(),sz);
}
static void md5AppendVector(Md5& md5,const vector<string>& v){
unsigned int sz=v.size();
unsigned char c[4];
c[0]=sz;
c[1]=sz>>8;
c[2]=sz>>16;
c[3]=sz>>24;
md5.update(c,4);
for(unsigned int i=0;i<sz;i++){
md5AppendString(md5,v[i]);
}
}
static void md5AppendMap(Md5& md5,const map<string,vector<string> >& m){
unsigned int sz=m.size();
unsigned char c[4];
c[0]=sz;
c[1]=sz>>8;
c[2]=sz>>16;
c[3]=sz>>24;
md5.update(c,4);
for(map<string,vector<string> >::const_iterator it=m.begin();it!=m.end();++it){
md5AppendString(md5,it->first);
md5AppendVector(md5,it->second);
}
}
unsigned char* TreeStorageNode::calcMD5(unsigned char* md){
unsigned char digest[16];
Md5 md5;
md5.init();
md5AppendString(md5,name);
md5AppendVector(md5,value);
md5AppendMap(md5,attributes);
for(unsigned int i=0;i<subNodes.size();i++){
TreeStorageNode *node=subNodes[i];
if(node==NULL){
memset(digest,0,16);
}else{
node->calcMD5(digest);
}
md5.update(digest,16);
}
return md5.final(md);
}
diff --git a/src/TreeStorageNode.h b/src/TreeStorageNode.h
index ca6a3fc..4c54101 100644
--- a/src/TreeStorageNode.h
+++ b/src/TreeStorageNode.h
@@ -1,93 +1,93 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TREESTORAGENODE_H
#define TREESTORAGENODE_H
#include "ITreeStorage.h"
#include <map>
#include <vector>
#include <string>
//This class is used to store data in a tree-structured way.
//Every (TreeStorage)Node has a vector with subNodes and every Node contains a hashmap with attributes.
class TreeStorageNode:public ITreeStorageBuilder,public ITreeStorageReader{
private:
//Iterator used to iterate the hashmap with attributes.
//Used by the methods getNextAttribute and getNextNode.
std::map<std::string,std::vector<std::string> >::iterator objAttrIterator;
public:
//Vector containing the subnodes of the TreeStorageNode.
std::vector<TreeStorageNode*> subNodes;
//String containing the name of the TreeStorageNode.
std::string name;
//Vector containing the value(s) of the TreeStorageNode.
std::vector<std::string> value;
//Hashmap containing the attributes of the TreeStorageNode.
std::map<std::string,std::vector<std::string> > attributes;
//Constructor.
TreeStorageNode(){}
//Destructor.
virtual ~TreeStorageNode();
//This method is used to destroy the TreeStorageNode.
//Also called when the deconstructor is called.
void destroy();
//Set the name of the TreeStorageNode.
//name: The name to give.
- virtual bool setName(std::string& name);
+ virtual bool setName(const std::string& name, const FilePosition& pos);
//Sets the parameter name to the name of the TreeStorageNode.
//name: The string to fill with the name;
virtual void getName(std::string& name);
//Set the value of the TreeStorageNode.
//value: The value to give.
- virtual bool setValue(std::vector<std::string>& value);
+ virtual bool setValue(const std::vector<std::string>& value, const std::vector<FilePosition>& pos);
//Sets the parameter value to the value of the TreeStorageNode.
//value: The string to fill with the name;
virtual void getValue(std::vector<std::string>& value);
//Creates a new node in the TreeStorageNode.
//The new node will be added to the subnodes.
//Returns: a pointer to the new node.
virtual ITreeStorageBuilder* newNode();
//Creates a new attribute in the TreeStorageNode.
//The attribute will be added to the attributes map.
//name: The name for the new attribute.
//value: The value for the new attribute.
- virtual bool newAttribute(std::string& name,std::vector<std::string>& value);
+ virtual bool newAttribute(const std::string& name, const std::vector<std::string>& value, const FilePosition& namePos, const std::vector<FilePosition>& valuePos);
+
-
//Method used for iterating through the attributes of the TreeStorageNode.
//pUserData: Pointer to the iterator.
//name: The string fill with the name of the attribute.
//value: Vector to fill with the value(s) of the attribute.
virtual void* getNextAttribute(void* pUserData,std::string& name,std::vector<std::string>& value);
//Method used for iterating through the subnodes of the TreeStorageNode.
//pUserData: Pointer to the iterator.
//obj: Pointer that will be pointed to the nextNode, if present.
virtual void* getNextNode(void* pUserData,ITreeStorageReader*& obj);
//Calculate the MD5 of node based on the data structure.
//places the message digest in md,
//which must have space for 16 bytes of output (or NULL).
unsigned char* calcMD5(unsigned char* md);
};
#endif

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 16, 5:06 AM (10 h, 39 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
62949
Default Alt Text
(149 KB)

Event Timeline